blob: 17252c3242c0ded55c7f228902e2a3b68dc3a01c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Google Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Xenos (Google) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.pde.internal.runtime.spy.dialogs;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.databinding.observable.list.ComputedList;
import org.eclipse.core.databinding.observable.sideeffect.ISideEffectFactory;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.databinding.swt.*;
import org.eclipse.jface.databinding.viewers.*;
import org.eclipse.jface.layout.*;
import org.eclipse.jface.resource.*;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.internal.runtime.PDERuntimePluginImages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
/**
* Implementation of the "layout spy" dialog, a diagnostic tool for fixing bugs
* related to control positioning and the implementation of SWT {@link Control}s
* and {@link Layout}s.
*/
public class LayoutSpyDialog {
private static final int EDGE_SIZE = 4;
private static final RGB SELECTED_PARENT_OVERLAY_COLOR = new RGB(255, 0, 0);
private static final RGB SELECTED_CHILD_OVERLAY_COLOR = new RGB(255, 255, 0);
/**
* Value used to indicate an unknown hint value
*/
private static final int UNKNOWN = -2;
private Shell shell;
// Controls
private TableViewer childList;
private Text details;
private Button selectWidgetButton;
private Button goUpButton;
private Button goDownButton;
private Shell overlay;
// Model
private WritableValue<@Nullable Composite> parentControl = new WritableValue<>(null, null);
private WritableValue<Boolean> controlSelectorOpen = new WritableValue<>(Boolean.FALSE, null);
private ComputedList<Control> listContents;
private IViewerObservableValue selectedChild;
private Color parentRectangleColor;
private Color childRectangleColor;
private ResourceManager resources;
private Region region;
private ISWTObservableValue overlayEnabled;
private Image upImage;
private Text diagnostics;
/**
* Creates the dialog but does not make it visible.
*
* @param parentShell
* the parent shell
*/
public LayoutSpyDialog(Shell parentShell) {
overlay = new Shell(SWT.ON_TOP | SWT.NO_TRIM);
{
overlay.addPaintListener(this::paintOverlay);
region = new Region();
overlay.addDisposeListener((DisposeEvent) -> {
region.dispose();
});
overlay.setRegion(region);
}
shell = new Shell(parentShell, SWT.SHELL_TRIM);
resources = new LocalResourceManager(JFaceResources.getResources(), shell);
parentRectangleColor = resources.createColor(SELECTED_PARENT_OVERLAY_COLOR);
childRectangleColor = resources.createColor(SELECTED_CHILD_OVERLAY_COLOR);
upImage = resources.createImage(PDERuntimePluginImages.UP_NAV);
Composite infoRegion = new Composite(shell, SWT.NONE);
{
Composite headerRegion = new Composite(infoRegion, SWT.NONE);
{
Button upButton = new Button(headerRegion, SWT.PUSH | SWT.CENTER);
upButton.setImage(upImage);
upButton.addListener(SWT.Selection, event -> goUp());
GridDataFactory.fillDefaults().applyTo(upButton);
Label childrenLabel = new Label(headerRegion, SWT.NONE);
childrenLabel.setText(Messages.LayoutSpyDialog_label_children);
}
GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(headerRegion);
Label detailsLabel = new Label(infoRegion, SWT.NONE);
detailsLabel.setText(Messages.LayoutSpyDialog_label_layout);
childList = new TableViewer(infoRegion);
details = new Text(infoRegion, SWT.READ_ONLY | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
GridDataFactory.fillDefaults().hint(300, 300).grab(true, true).applyTo(details);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true).generateLayout(infoRegion);
}
diagnostics = new Text(shell, SWT.READ_ONLY | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
GridDataFactory.fillDefaults().hint(300, 300).grab(true, true).applyTo(diagnostics);
Button showOverlayButton = new Button(shell, SWT.CHECK);
showOverlayButton.setText(Messages.LayoutSpyDialog_button_show_overlay);
Composite buttonBar = new Composite(shell, SWT.NONE);
{
selectWidgetButton = new Button(buttonBar, SWT.PUSH);
selectWidgetButton.setText(Messages.LayoutSpyDialog_button_select_control);
goUpButton = new Button(buttonBar, SWT.PUSH);
goUpButton.setText(Messages.LayoutSpyDialog_button_open_parent);
goDownButton = new Button(buttonBar, SWT.PUSH);
goDownButton.setText(Messages.LayoutSpyDialog_button_open_child);
GridLayoutFactory.fillDefaults().numColumns(3).generateLayout(buttonBar);
}
GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(buttonBar);
GridLayoutFactory.fillDefaults().margins(LayoutConstants.getMargins()).generateLayout(shell);
// Attach listeners
shell.addDisposeListener(event -> disposed());
selectWidgetButton.addListener(SWT.Selection, event -> selectControl());
goUpButton.addListener(SWT.Selection, event -> goUp());
goDownButton.addListener(SWT.Selection, event -> goDown());
childList.addOpenListener(event -> {
goDown();
});
// Set up the model
selectedChild = ViewerProperties.singleSelection().observe(childList);
overlayEnabled = WidgetProperties.selection().observe(showOverlayButton);
childList.setContentProvider(new ObservableListContentProvider());
listContents = new ComputedList<Control>() {
@Override
protected List<Control> calculate() {
Composite control = parentControl.getValue();
if (control == null) {
return Arrays.asList(Display.getCurrent().getShells());
}
return Arrays.asList(control.getChildren());
}
};
childList.setInput(listContents);
ISideEffectFactory sideEffectFactory = WidgetSideEffects.createFactory(shell);
sideEffectFactory.create(this::computeParentInfo, details::setText);
sideEffectFactory.create(this::computeChildInfo, diagnostics::setText);
sideEffectFactory.create(this::updateOverlay);
openComposite(parentShell);
}
/**
* Opens the dialog box, revealing it to the user.
*/
public void open() {
this.shell.pack();
this.shell.open();
}
/**
* Disposes the dialog box.
*/
public void close() {
this.shell.dispose();
}
/**
* Invoked as a callback when the main shell is disposed.
*/
private void disposed() {
listContents.dispose();
selectedChild.dispose();
parentControl.dispose();
overlay.dispose();
}
private void openComposite(Composite composite) {
parentControl.setValue(composite);
}
/**
* Returns the currently-selected child control or null if none.
*/
private @Nullable Control getSelectedChild() {
return (@Nullable Control) selectedChild.getValue();
}
/**
* Opens the given control in the layout spy.
*/
@SuppressWarnings("unchecked")
private void openControl(Control control) {
Composite parent = control.getParent();
if (parent == null) {
if (control instanceof Composite) {
parentControl.setValue((Composite) control);
}
} else {
parentControl.setValue(parent);
selectedChild.setValue(control);
}
}
// Overlay management
// -----------------------------------------------------------------
/**
* This callback is used to update the bounds and visible region for the
* overlay shell. It is used as part of a side-effect, so if it makes use of
* any tracked getters, it will automatically be invoked again whenever one
* of those tracked getters changes state.
*
* @TrackedGetter
*/
public void updateOverlay() {
@Nullable
Composite parent = parentControl.getValue();
boolean enabled = Boolean.TRUE.equals(overlayEnabled.getValue());
overlay.setVisible(parent != null && !controlSelectorOpen.getValue() && enabled);
if (parent == null) {
return;
}
Shell shell = parent.getShell();
Rectangle outerBounds = Geometry.copy(shell.getBounds());
overlay.setBounds(outerBounds);
Rectangle parentBoundsWrtDisplay = GeometryUtil.getDisplayBounds(parent);
Rectangle parentBoundsWrtOverlay = Geometry.toControl(overlay, parentBoundsWrtDisplay);
Rectangle innerBoundsWrtOverlay = Geometry.copy(parentBoundsWrtOverlay);
Geometry.expand(innerBoundsWrtOverlay, -EDGE_SIZE, -EDGE_SIZE, -EDGE_SIZE, -EDGE_SIZE);
region.dispose();
region = new Region();
@Nullable
Control child = (@Nullable Control) selectedChild.getValue();
if (child != null) {
Rectangle childBoundsWrtOverlay = Geometry.toControl(overlay, GeometryUtil.getDisplayBounds(child));
Rectangle childInnerBoundsWrtOverlay = Geometry.copy(childBoundsWrtOverlay);
Geometry.expand(childInnerBoundsWrtOverlay, -EDGE_SIZE, -EDGE_SIZE, -EDGE_SIZE, -EDGE_SIZE);
region.add(parentBoundsWrtOverlay);
int distanceToTop = childBoundsWrtOverlay.y - innerBoundsWrtOverlay.y;
subtractRect(region, GeometryUtil.extrudeEdge(innerBoundsWrtOverlay, distanceToTop, SWT.TOP));
int distanceToLeft = childBoundsWrtOverlay.x - innerBoundsWrtOverlay.x;
subtractRect(region, GeometryUtil.extrudeEdge(innerBoundsWrtOverlay, distanceToLeft, SWT.LEFT));
int distanceToRight = GeometryUtil.getRight(innerBoundsWrtOverlay) - GeometryUtil.getRight(childBoundsWrtOverlay);
subtractRect(region, GeometryUtil.extrudeEdge(innerBoundsWrtOverlay, distanceToRight, SWT.RIGHT));
int distanceToBottom = GeometryUtil.getBottom(innerBoundsWrtOverlay) - GeometryUtil.getBottom(childBoundsWrtOverlay);
subtractRect(region, GeometryUtil.extrudeEdge(innerBoundsWrtOverlay, distanceToBottom, SWT.BOTTOM));
subtractRect(region, childInnerBoundsWrtOverlay);
} else {
region.add(parentBoundsWrtOverlay);
region.subtract(innerBoundsWrtOverlay);
}
overlay.redraw();
overlay.setRegion(region);
}
/**
* Paint callback for the overlay shell. This draws rectangles around the
* selected layout and the selected child.
*/
protected void paintOverlay(PaintEvent e) {
@Nullable
Composite parent = parentControl.getValue();
if (parent == null) {
return;
}
int halfSize = EDGE_SIZE / 2;
Rectangle parentDisplayBounds = GeometryUtil.getDisplayBounds(parent);
Rectangle parentBoundsWrtOverlay = Geometry.toControl(overlay, parentDisplayBounds);
Geometry.expand(parentBoundsWrtOverlay, -halfSize, -halfSize, -halfSize, -halfSize);
@Nullable
Control child = (@Nullable Control) selectedChild.getValue();
e.gc.setLineWidth(EDGE_SIZE);
e.gc.setForeground(parentRectangleColor);
e.gc.drawRectangle(parentBoundsWrtOverlay.x, parentBoundsWrtOverlay.y, parentBoundsWrtOverlay.width,
parentBoundsWrtOverlay.height);
if (child != null) {
Rectangle childBoundsWrtOverlay = Geometry.toControl(overlay, GeometryUtil.getDisplayBounds(child));
Geometry.expand(childBoundsWrtOverlay, -halfSize, -halfSize, -halfSize, -halfSize);
e.gc.setForeground(childRectangleColor);
e.gc.drawRectangle(childBoundsWrtOverlay.x, childBoundsWrtOverlay.y, childBoundsWrtOverlay.width,
childBoundsWrtOverlay.height);
}
}
// User gesture callbacks
// -----------------------------------------------------------
/**
* Invoked when the user clicks the "select control" button. It opens some
* UI that allows the user to select a new input control for the layout spy.
*/
private void selectControl() {
this.controlSelectorOpen.setValue(true);
this.shell.setVisible(false);
new ControlSelector((@Nullable Control control) -> {
if (control != null) {
openControl(control);
}
this.controlSelectorOpen.setValue(false);
this.shell.setVisible(true);
});
}
/**
* Invoked when the user clicks the "go up" button, which opens the parent.
*/
@SuppressWarnings("unchecked")
private void goUp() {
@Nullable
Composite parent = parentControl.getValue();
if (parent == null) {
return;
}
Composite ancestor = parent.getParent();
openComposite(ancestor);
this.selectedChild.setValue(parent);
}
/**
* Invoked when the user clicks the "go down" button, which opens the
* selected child.
*/
private void goDown() {
Control child = getSelectedChild();
if (child instanceof Composite) {
Composite composite = (Composite) child;
openComposite(composite);
}
}
// Utility functions -----------------------------------------------------
/**
* Subtracts the given rectangle from the given region unless the rectangle
* is empty.
*/
private static void subtractRect(Region region, Rectangle rect) {
if (rect.isEmpty()) {
return;
}
region.subtract(rect);
}
private String getWarningMessage(String string) {
return NLS.bind(Messages.LayoutSpyDialog_warning_prefix, string);
}
private static String printHint(int hint) {
if (hint == SWT.DEFAULT) {
return "SWT.DEFAULT"; //$NON-NLS-1$
}
return Integer.toString(hint);
}
private static String printPoint(Point toPrint) {
return NLS.bind("({0}, {1})", new Object[] { toPrint.x, toPrint.y }); //$NON-NLS-1$
}
// Control classification ------------------------------------------------
private static boolean isHorizontallyScrollable(Control child) {
return (child.getStyle() & SWT.H_SCROLL) != 0;
}
private static boolean isVerticallyScrollable(Control child) {
return (child.getStyle() & SWT.V_SCROLL) != 0;
}
/**
* Computes the values that should be subtracted off the width and height
* hints from computeSize on the given control.
*/
private static Point computeHintAdjustment(Control control) {
int widthAdjustment;
int heightAdjustment;
if (control instanceof Scrollable) {
// For composites, subtract off the trim size
Scrollable composite = (Scrollable) control;
Rectangle trim = composite.computeTrim(0, 0, 0, 0);
widthAdjustment = trim.width;
heightAdjustment = trim.height;
} else {
// For non-composites, subtract off 2 * the border size
widthAdjustment = control.getBorderWidth() * 2;
heightAdjustment = widthAdjustment;
}
return new Point(widthAdjustment, heightAdjustment);
}
/**
* Returns true if the given control is a composite which can expand in the
* given dimension. Returns false if the control either cannot expand in the
* given dimension or if its growable characteristics cannot be computed in
* that dimension.
*/
private static boolean isGrowableLayout(Control control, boolean horizontal) {
if (control instanceof Composite) {
Composite composite = (Composite) control;
Layout theLayout = composite.getLayout();
if (theLayout instanceof GridLayout) {
Control[] children = composite.getChildren();
for (int i = 0; i < children.length; i++) {
Control child = children[i];
GridData data = (GridData) child.getLayoutData();
if (data != null) {
if (horizontal) {
if (data.grabExcessHorizontalSpace) {
return true;
}
} else {
if (data.grabExcessVerticalSpace) {
return true;
}
}
}
}
}
}
return false;
}
/**
* Returns true iff another visible widget in the same shell overlaps the
* given control.
*/
private static boolean overlapsSibling(Control toFind) {
Composite parent = toFind.getParent();
Control current = toFind;
Rectangle displayBounds = GeometryUtil.getDisplayBounds(toFind);
while (parent != null && !(parent instanceof Shell)) {
for (Control nextSibling : parent.getChildren()) {
if (nextSibling == current) {
continue;
}
if (!nextSibling.isVisible()) {
continue;
}
Rectangle nextSiblingBounds = GeometryUtil.getDisplayBounds(nextSibling);
if (nextSiblingBounds.intersects(displayBounds)) {
return true;
}
}
current = parent;
parent = parent.getParent();
}
return false;
}
/**
* Computes the string that will be shown in the text box which displays
* information about the selected child. This is a tracked getter -- if it
* reads from a databinding observable, the text box will automatically
* refresh in response to changes in that observable.
*
* @TrackedGetter
*/
private String computeChildInfo() {
StringBuilder builder = new StringBuilder();
Control child = getSelectedChild();
if (child != null) {
builder.append(child.getClass().getName());
builder.append("\n\n"); //$NON-NLS-1$
int widthHintFromLayoutData = UNKNOWN;
int heightHintFromLayoutData = UNKNOWN;
Object layoutData = child.getLayoutData();
if (layoutData == null) {
builder.append("getLayoutData() == null\n"); //$NON-NLS-1$
} else if (layoutData instanceof GridData) {
GridData grid = (GridData) layoutData;
builder.append(GridDataFactory.createFrom(grid));
widthHintFromLayoutData = grid.widthHint;
heightHintFromLayoutData = grid.heightHint;
if (!grid.grabExcessHorizontalSpace) {
if (isHorizontallyScrollable(child) || isGrowableLayout(child, true)) {
builder.append(getWarningMessage(
Messages.LayoutSpyDialog_warning_grab_horizontally_scrolling));
}
}
if (!grid.grabExcessVerticalSpace) {
if (isVerticallyScrollable(child) || isGrowableLayout(child, false)) {
builder.append(getWarningMessage(
Messages.LayoutSpyDialog_warning_grab_vertical_scrolling));
}
}
} else if (layoutData instanceof FormData) {
FormData data = (FormData) layoutData;
widthHintFromLayoutData = data.width;
heightHintFromLayoutData = data.height;
} else {
describeObject(builder, "data", layoutData); //$NON-NLS-1$
}
if (isHorizontallyScrollable(child)) {
if (widthHintFromLayoutData == SWT.DEFAULT) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_hint_for_horizontally_scrollable));
}
}
if (isVerticallyScrollable(child)) {
if (heightHintFromLayoutData == SWT.DEFAULT) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_hint_for_vertically_scrollable));
}
}
builder.append("\n"); //$NON-NLS-1$
// Print the current dimensions
Rectangle bounds = child.getBounds();
builder.append(NLS.bind("getBounds() = {0}", bounds.toString())); //$NON-NLS-1$
builder.append("\n"); //$NON-NLS-1$
Point adjustment = computeHintAdjustment(child);
int widthHint = Math.max(0, bounds.width - adjustment.x);
int heightHint = Math.max(0, bounds.height - adjustment.y);
builder.append(NLS.bind("widthAdjustment = {0}, heightAdjustment = {1}", //$NON-NLS-1$
new Object[] { adjustment.x, adjustment.y }));
builder.append("\n\n"); //$NON-NLS-1$
// Print the default size
Point defaultSize = child.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
builder.append(NLS.bind("computeSize(SWT.DEFAULT, SWT.DEFAULT, false) = {0}", printPoint(defaultSize))); //$NON-NLS-1$
builder.append("\n"); //$NON-NLS-1$
// Print the preferred horizontally-wrapped size:
Point hWrappedSize = child.computeSize(widthHint, SWT.DEFAULT, false);
builder.append(NLS.bind("computeSize({0} - widthAdjustment, SWT.DEFAULT, false) = {1}", //$NON-NLS-1$
new Object[] { widthHint, printPoint(hWrappedSize) }));
builder.append("\n"); //$NON-NLS-1$
// Print the preferred vertically-wrapped size:
Point vWrappedSize = child.computeSize(SWT.DEFAULT, heightHint, false);
builder.append(NLS.bind("computeSize(SWT.DEFAULT, {0} - heightAdjustment, false) = {1}", //$NON-NLS-1$
new Object[] { heightHint, printPoint(vWrappedSize) }));
builder.append("\n"); //$NON-NLS-1$
// Check for warnings
Point noOpSize = child.computeSize(widthHint, heightHint, false);
if (noOpSize.x != bounds.width || noOpSize.y != bounds.height) {
builder.append(getWarningMessage(NLS.bind(Messages.LayoutSpyDialog_warning_unexpected_compute_size,
new Object[] { printHint(widthHint), printHint(heightHint), printPoint(noOpSize) })));
}
if (bounds.height < hWrappedSize.y) {
builder.append(
getWarningMessage(Messages.LayoutSpyDialog_warning_shorter_than_preferred_size));
}
printReasonControlIsInvisible(builder, child);
}
return builder.toString();
}
/**
* If the control cannot be seen by the user, this method adds a warning
* message to the given builder explaining the reason why the control cannot
* be seen.
*/
private void printReasonControlIsInvisible(StringBuilder builder, Control control) {
if (!control.isVisible()) {
builder.append(getWarningMessage("isVisible() == false")); //$NON-NLS-1$
return;
}
Rectangle bounds = control.getBounds();
if (bounds.isEmpty()) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_zero_size));
return;
}
Rectangle displayBounds = GeometryUtil.getDisplayBounds(control);
Composite parent = control.getParent();
if (parent != null) {
Rectangle parentDisplayBounds = GeometryUtil.getDisplayBounds(parent);
Rectangle intersection = displayBounds.intersection(parentDisplayBounds);
if (intersection.isEmpty()) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_bounds_outside_parent));
return;
}
if (intersection.width < bounds.width || intersection.height < bounds.height) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_control_partially_clipped));
return;
}
if (overlapsSibling(control)) {
builder.append(getWarningMessage(Messages.LayoutSpyDialog_warning_control_overlaps_siblings));
return;
}
}
}
/**
* Computes the string that will be shown in the text box which displays
* information about the selected layout. This is a tracked getter: if it
* reads from an observable, the text box will update automatically when the
* observable changes.
*
* @TrackedGetter
*/
private String computeParentInfo() {
StringBuilder builder = new StringBuilder();
@Nullable
Composite parent = parentControl.getValue();
if (parent != null) {
builder.append(parent.getClass().getName());
builder.append("\n\n"); //$NON-NLS-1$
Rectangle parentBounds = GeometryUtil.getDisplayBounds(parent);
Layout layout = parent.getLayout();
if (layout != null) {
if (layout instanceof GridLayout) {
GridLayout grid = (GridLayout) layout;
builder.append(GridLayoutFactory.createFrom(grid));
boolean hasVerticallyTruncadeControls = false;
boolean hasHorizontallyTruncadeControls = false;
boolean hasHorizontalGrab = false;
boolean hasVerticalGrab = false;
for (Control next : parent.getChildren()) {
@Nullable
GridData data = (GridData) next.getLayoutData();
if (data == null) {
continue;
}
Rectangle childBounds = GeometryUtil.getDisplayBounds(parent);
Rectangle intersection = childBounds.intersection(parentBounds);
if (intersection.width < childBounds.width) {
hasHorizontallyTruncadeControls = true;
}
if (intersection.height < childBounds.height) {
hasVerticallyTruncadeControls = true;
}
hasHorizontalGrab = hasHorizontalGrab || data.grabExcessHorizontalSpace;
hasVerticalGrab = hasVerticalGrab || data.grabExcessVerticalSpace;
}
if (hasHorizontallyTruncadeControls && !hasHorizontalGrab) {
builder.append(getWarningMessage(
Messages.LayoutSpyDialog_warning_not_grabbing_horizontally));
}
if (hasVerticallyTruncadeControls && !hasVerticalGrab) {
builder.append(getWarningMessage(
Messages.LayoutSpyDialog_warning_not_grabbing_vertically));
}
} else {
describeObject(builder, "layout", layout); //$NON-NLS-1$
}
}
} else {
builder.append(Messages.LayoutSpyDialog_label_no_parent_control_selected);
}
return builder.toString();
}
/**
* Uses reflection to print the values of the given object's public fields.
*/
void describeObject(StringBuilder result, String variableName, Object toDescribe) {
@SuppressWarnings("rawtypes")
Class clazz = toDescribe.getClass();
result.append(clazz.getName());
result.append(" "); //$NON-NLS-1$
result.append(variableName);
result.append(";\n"); //$NON-NLS-1$
Field[] fields = clazz.getFields();
for (Field nextField : fields) {
int modifiers = nextField.getModifiers();
if (!Modifier.isPublic(modifiers)) {
continue;
}
try {
String next = variableName + "." + nextField.getName() + " = " + nextField.get(toDescribe) + ";"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
result.append(next);
result.append("\n"); //$NON-NLS-1$
} catch (IllegalArgumentException | IllegalAccessException e) {
// Don't care
}
}
}
}