Repository: wicket Updated Branches: refs/heads/WICKET-6055-non-blocking-lazy [created] cfb75dd64
WICKET-6055 non-blocking lazy loading Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/cfb75dd6 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/cfb75dd6 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/cfb75dd6 Branch: refs/heads/WICKET-6055-non-blocking-lazy Commit: cfb75dd64d54972a9b604101ff840c7c5cf80d35 Parents: eb64b2a Author: Sven Meier <svenme...@apache.org> Authored: Fri Oct 20 14:25:15 2017 +0200 Committer: Sven Meier <svenme...@apache.org> Committed: Fri Oct 20 14:25:15 2017 +0200 ---------------------------------------------------------------------- .../wicket/ajax/AbstractAjaxTimerBehavior.java | 16 +- .../examples/ajax/builtin/LazyLoadingPage.html | 21 +- .../examples/ajax/builtin/LazyLoadingPage.java | 137 ++++++++-- .../ajax/markup/html/AjaxLazyLoadPanel.java | 258 ++++++++++++------- .../markup/html/AjaxLazyLoadPanelTester.java | 10 +- .../html/AjaxLazyLoadPanelTesterTest.java | 2 +- .../org/apache/wicket/util/value/LongValue.java | 21 ++ 7 files changed, 331 insertions(+), 134 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxTimerBehavior.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxTimerBehavior.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxTimerBehavior.java index 0c9a276..10dbb9b 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxTimerBehavior.java +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxTimerBehavior.java @@ -112,6 +112,8 @@ public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehav @Override protected final void respond(final AjaxRequestTarget target) { + Component component = getComponent(); + if (shouldTrigger()) { onTimer(target); @@ -124,7 +126,7 @@ public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehav } } - clearTimeout(target.getHeaderResponse()); + clearTimeout(component, target.getHeaderResponse()); } /** @@ -181,9 +183,9 @@ public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehav headerResponse.render(OnLoadHeaderItem.forScript(getJsTimeoutCall(updateInterval))); } - private void clearTimeout(IHeaderResponse headerResponse) + private void clearTimeout(Component component, IHeaderResponse headerResponse) { - headerResponse.render(OnLoadHeaderItem.forScript("Wicket.Timer.clear('" + getComponent().getMarkupId() + "');")); + headerResponse.render(OnLoadHeaderItem.forScript("Wicket.Timer.clear('" + component.getMarkupId() + "');")); } /** @@ -200,7 +202,7 @@ public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehav if (target != null) { - clearTimeout(target.getHeaderResponse()); + clearTimeout(getComponent(), target.getHeaderResponse()); } } } @@ -208,13 +210,15 @@ public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehav @Override public void onRemove(Component component) { - component.getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(target.getHeaderResponse())); + component.getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(component, target.getHeaderResponse())); } @Override protected void onUnbind() { - getComponent().getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(target.getHeaderResponse())); + Component component = getComponent(); + + component.getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(component, target.getHeaderResponse())); } /** http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.html ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.html index 821833d..576b194 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.html +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.html @@ -1,12 +1,27 @@ <?xml version="1.0" encoding="UTF-8" ?> <wicket:extend xmlns:wicket="http://wicket.apache.org"> +<p> This example demonstrates the AjaxLazyLoadPanel It will lazy load a panel after the page is first fully rendered. So panels that can take a while too create can be lazy created by an ajax call after the page is rendered. - -<br/><br/> +</p> + +<div wicket:id="nonblocking"> + <h2>Non-blocking lazy panels</h2> + + <p><a href="#" wicket:id="start">Start non-blocking panels</a> (<a href="#" wicket:id="startAjax">via Ajax</a>)</p> + + <div wicket:id="repeater"></div> +</div> + +<div wicket:id="blocking"> + <h2>Blocking lazy panels</h2> + + <p><a href="#" wicket:id="start">Start blocking panels</a> (<a href="#" wicket:id="startAjax">via Ajax</a>)</p> + + <div wicket:id="repeater"></div> +</div> -<div wicket:id="lazy"></div> </wicket:extend> http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.java ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.java index 23b4403..ddf1de6 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.java +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/LazyLoadingPage.java @@ -16,38 +16,139 @@ */ package org.apache.wicket.examples.ajax.builtin; -import org.apache.wicket.Component; +import java.util.Random; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.extensions.ajax.markup.html.AjaxLazyLoadPanel; +import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.markup.repeater.RepeatingView; +import org.apache.wicket.util.time.Duration; -/** - * @author jcompagner - */ +@SuppressWarnings({ "javadoc", "serial" }) public class LazyLoadingPage extends BasePage { - /** - * Construct. - */ + private Random r = new Random(); + private WebMarkupContainer nonblocking; + private WebMarkupContainer blocking; + private RepeatingView blockingRepeater; + private RepeatingView nonBlockingRepeater; + public LazyLoadingPage() { - add(new AjaxLazyLoadPanel("lazy") + nonblocking = new WebMarkupContainer("nonblocking"); + nonblocking.setOutputMarkupId(true); + add(nonblocking); + + nonblocking.add(new Link<Void>("start") + { + @Override + public void onClick() + { + addNonBlockingPanels(); + } + }); + nonblocking.add(new AjaxLink<Void>("startAjax") + { + @Override + public void onClick(AjaxRequestTarget target) + { + addNonBlockingPanels(); + } + }); + + nonBlockingRepeater = new RepeatingView("repeater"); + nonblocking.add(nonBlockingRepeater); + + blocking = new WebMarkupContainer("blocking"); + blocking.setOutputMarkupId(true); + add(blocking); + + blocking.add(new Link<Void>("start") { - @Override - public Component getLazyLoadComponent(String id) + public void onClick() { - // sleep for 5 seconds to show the behavior - try + addBlockingPanels(); + } + }); + blocking.add(new AjaxLink<Void>("startAjax") + { + @Override + public void onClick(AjaxRequestTarget target) + { + addBlockingPanels(); + } + }); + + blockingRepeater = new RepeatingView("repeater"); + blocking.add(blockingRepeater); + } + + private void addNonBlockingPanels() + { + nonBlockingRepeater.removeAll(); + + for (int i = 0; i < 10; i++) + nonBlockingRepeater.add(new AjaxLazyLoadPanel<Label>(nonBlockingRepeater.newChildId()) + { + private static final long serialVersionUID = 1L; + + private long startTime = System.currentTimeMillis(); + + private int seconds = r.nextInt(10); + + @Override + protected boolean isContentReady() { - Thread.sleep(5000); + return Duration.milliseconds(System.currentTimeMillis() - startTime) + .seconds() > seconds; } - catch (InterruptedException e) + + @Override + protected Duration getUpdateInterval() { - throw new RuntimeException(e); + return Duration.milliseconds(seconds * 1000 / 10); } - return new Label(id, "Lazy Loaded after 5 seconds"); - } - }); + @Override + public Label createContentComponent(String id) + { + return new Label(id, "Lazy Loaded after " + seconds + " seconds"); + } + }); + + getRequestCycle().find(AjaxRequestTarget.class).ifPresent(t -> t.add(nonblocking)); + } + + private void addBlockingPanels() + { + blockingRepeater.removeAll(); + + for (int i = 0; i < 5; i++) + blockingRepeater.add(new AjaxLazyLoadPanel<Label>(blockingRepeater.newChildId()) + { + private static final long serialVersionUID = 1L; + + private int seconds = r.nextInt(5); + + @Override + public Label createContentComponent(String markupId) + { + try + { + Thread.sleep(seconds * 1000); + } + catch (InterruptedException e) + { + } + return new Label(markupId, + "Lazy loaded after blocking the Wicket thread for " + seconds + " seconds"); + } + }); + + getRequestCycle().find(AjaxRequestTarget.class).ifPresent(t -> t.add(blocking)); } } http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.java index ce473ad..01cef17 100644 --- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.java +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.java @@ -16,42 +16,48 @@ */ package org.apache.wicket.extensions.ajax.markup.html; +import java.util.List; +import java.util.Optional; + import org.apache.wicket.Component; +import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; -import org.apache.wicket.markup.head.IHeaderResponse; -import org.apache.wicket.markup.head.OnDomReadyHeaderItem; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler; +import org.apache.wicket.util.time.Duration; +import org.apache.wicket.util.visit.IVisit; +import org.apache.wicket.util.visit.IVisitor; /** - * A panel where you can lazy load another panel. This can be used if you have a panel/component - * that is pretty heavy in creation and you first want to show the user the page and then replace - * the panel when it is ready. - * - * @author jcompagner + * A panel which load lazily a single content component. This can be used if you have a + * component that is pretty heavy in creation and you first want to show the user the page and + * then replace the panel when it is ready. + * <p> + * This panel will wait with adding the content until {@link #isContentReady()} returns + * {@code true}. It will poll using an AJAX timer behavior that is installed on the page. When the + * component is replaced, the timer stops. When you have multiple {@code AjaxLazyLoadPanel}s on the + * same page, only one timer is used and all panels piggyback on this single timer. + * <p> + * This component will also replace the contents when a normal request comes through and the + * content is ready. * * @since 1.3 */ -public abstract class AjaxLazyLoadPanel extends Panel +public abstract class AjaxLazyLoadPanel<T extends Component> extends Panel { private static final long serialVersionUID = 1L; /** * The component id which will be used to load the lazily loaded component. */ - public static final String LAZY_LOAD_COMPONENT_ID = "content"; + private static final String CONTENT_ID = "content"; - // state, - // 0:add loading component - // 1:loading component added, waiting for ajax replace - // 2:ajax replacement completed - private byte state = 0; + private boolean loaded; /** * Constructor @@ -74,121 +80,173 @@ public abstract class AjaxLazyLoadPanel extends Panel super(id, model); setOutputMarkupId(true); - - add(new AbstractDefaultAjaxBehavior() - { - private static final long serialVersionUID = 1L; - - @Override - protected void respond(final AjaxRequestTarget target) - { - if (state < 2) - { - Component component = getLazyLoadComponent(LAZY_LOAD_COMPONENT_ID); - AjaxLazyLoadPanel.this.replace(component); - setState((byte) 2); - AjaxLazyLoadPanel.this.onComponentLoaded(component, target); - } - target.add(AjaxLazyLoadPanel.this); - - } - - @Override - protected void updateAjaxAttributes(AjaxRequestAttributes attributes) - { - super.updateAjaxAttributes(attributes); - AjaxLazyLoadPanel.this.updateAjaxAttributes(attributes); - } - - @Override - public void renderHead(final Component component, final IHeaderResponse response) - { - super.renderHead(component, response); - if (state < 2) - { - CharSequence js = getCallbackScript(component); - handleCallbackScript(response, js, component); - } - } - }); - } - - protected void updateAjaxAttributes(AjaxRequestAttributes attributes) - { } /** - * Allows subclasses to change the callback script if needed. + * Determines that the content we're waiting for is ready, typically used in polling background + * threads for their result. Override this to implement your own check. + * <p> + * This default implementation returns {@code true}, i.e. assuming the content is ready immediately. * - * @param response - * the current response that writes to the header - * @param callbackScript - * the JavaScript to write in the header - * @param component - * the component which produced the callback script - */ - protected void handleCallbackScript(final IHeaderResponse response, - final CharSequence callbackScript, final Component component) - { - response.render(OnDomReadyHeaderItem.forScript(callbackScript)); - } - - /** - * @see org.apache.wicket.Component#onBeforeRender() + * @return whether the actual content is ready */ - @Override - protected void onBeforeRender() + protected boolean isContentReady() { - if (state == 0) - { - add(getLoadingComponent(LAZY_LOAD_COMPONENT_ID)); - setState((byte)1); - } - super.onBeforeRender(); + return true; } /** + * Create a loading component shown instead of the actual content until it is {@link #isContentReady()}. * - * @param state + * @param markupId + * The components markupid. + * @return The component to show while the real content isn't ready yet */ - private void setState(final byte state) + protected Component createLoadingComponent(final String markupId) { - this.state = state; - getPage().dirty(); + IRequestHandler handler = new ResourceReferenceRequestHandler( + AbstractDefaultAjaxBehavior.INDICATOR); + return new Label(markupId, + "<img alt=\"Loading...\" src=\"" + RequestCycle.get().urlFor(handler) + "\"/>") + .setEscapeModelStrings(false); } /** + * Factory method for creating the lazily loaded content that replaces the loading component after + * {@link #isContentReady()} returns {@code true}. You may call setRenderBodyOnly(true) + * on this component if you need the body only. * * @param markupId * The components markupid. - * @return The component that must be lazy created. You may call setRenderBodyOnly(true) on this - * component if you need the body only. + * @return the content to show after {@link #isContentReady()} */ - public abstract Component getLazyLoadComponent(String markupId); + protected abstract T createContentComponent(String markupId); /** - * Called when the placeholder component is replaced with the lazy loaded one. + * Called after the loading component was replaced with the lazy loaded content. + * <p> + * This default implementation does nothing. * * @param component - * The lazy loaded component + * The lazy loaded content * @param target - * The Ajax request handler + * optional Ajax request handler */ - protected void onComponentLoaded(Component component, AjaxRequestTarget target) + protected void onContentLoaded(T component, Optional<AjaxRequestTarget> target) { } + @Override + protected void onInitialize() + { + super.onInitialize(); + + AjaxRequestTarget target = getRequestCycle().find(AjaxRequestTarget.class).orElse(null); + + AjaxLazyLoadTimer timer; + // when the timer is not yet installed add it + List<AjaxLazyLoadTimer> behaviors = getPage().getBehaviors(AjaxLazyLoadTimer.class); + if (behaviors.isEmpty()) { + timer = new AjaxLazyLoadTimer(); + getPage().add(timer); + if (target != null) { + // the timer will not be rendered, so stop it first + // and restart it immediately on the Ajax request + timer.stop(null); + timer.restart(target); + } + } + } + + @Override + protected void onConfigure() + { + super.onConfigure(); + + if (get(CONTENT_ID) == null) { + add(createLoadingComponent(CONTENT_ID)); + } else { + isLoaded(); + } + } + /** - * @param markupId - * The components markupid. - * @return The component to show while the real component is being created. + * Get the preferred interval for updates. + * <p> + * Since all LazyLoadingPanels on a page share the same Ajax timer, its update interval + * is derived from the minimum of all panel's update intervals. + * + * @return update interval */ - public Component getLoadingComponent(final String markupId) - { - IRequestHandler handler = new ResourceReferenceRequestHandler( - AbstractDefaultAjaxBehavior.INDICATOR); - return new Label(markupId, "<img alt=\"Loading...\" src=\"" + - RequestCycle.get().urlFor(handler) + "\"/>").setEscapeModelStrings(false); + protected Duration getUpdateInterval() { + return Duration.seconds(1); } + private boolean isLoaded() { + if (loaded == false) + { + if (isContentReady()) + { + loaded = true; + + // create the lazy load component + T content = createContentComponent(CONTENT_ID); + + // replace the spinner with the new component + AjaxLazyLoadPanel.this.replace(content); + + Optional<AjaxRequestTarget> target = getRequestCycle().find(AjaxRequestTarget.class); + + // notify our subclasses of the updated component + onContentLoaded(content, target); + + // repaint our selves if there's an AJAX request in play, otherwise let the page + // redraw itself + target.ifPresent(t -> t.add(AjaxLazyLoadPanel.this)); + } + } + + return loaded; + } + + /** + * The AJAX timer for updating the AjaxLazyLoadPanel. Is designed to be a page-local singleton + * running as long as LazyLoadPanels are still loading. + * + * @see AjaxLazyLoadPanel#isLoaded() + */ + private static class AjaxLazyLoadTimer extends AbstractAjaxTimerBehavior + { + private static final long serialVersionUID = 1L; + + public AjaxLazyLoadTimer() + { + super(Duration.ONE_SECOND); + } + + @Override + protected void onTimer(AjaxRequestTarget target) + { + setUpdateInterval(Duration.MAXIMUM); + + getComponent().getPage().visitChildren(AjaxLazyLoadPanel.class, new IVisitor<AjaxLazyLoadPanel<?>, Void>() + { + @Override + public void component(AjaxLazyLoadPanel<?> panel, IVisit<Void> visit) + { + if (panel.isLoaded() == false) { + setUpdateInterval(Duration.min(getUpdateInterval(), panel.getUpdateInterval())); + } + } + }); + + // all panels have completed their replacements, we can stop the timer + if (Duration.MAXIMUM.equals(getUpdateInterval())) + { + stop(target); + + getComponent().remove(this); + } + } + } } http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanelTester.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanelTester.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanelTester.java index a141108..81f0224 100644 --- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanelTester.java +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanelTester.java @@ -19,9 +19,9 @@ package org.apache.wicket.extensions.ajax.markup.html; import java.util.List; import org.apache.wicket.MarkupContainer; +import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior; import org.apache.wicket.behavior.AbstractAjaxBehavior; -import org.apache.wicket.behavior.Behavior; import org.apache.wicket.util.tester.BaseWicketTester; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; @@ -35,7 +35,6 @@ import org.slf4j.LoggerFactory; */ public class AjaxLazyLoadPanelTester { - private static final Logger logger = LoggerFactory.getLogger(AjaxLazyLoadPanelTester.class); /** @@ -59,12 +58,13 @@ public class AjaxLazyLoadPanelTester { // get the AbstractAjaxBehaviour which is responsible for // getting the contents of the lazy panel - List<AbstractAjaxBehavior> behaviors = component.getBehaviors(AbstractAjaxBehavior.class); + List<AbstractAjaxTimerBehavior> behaviors = component.getPage() + .getBehaviors(AbstractAjaxTimerBehavior.class); if (behaviors.size() == 0) { logger.warn("AjaxLazyLoadPanel child found, but no attached AbstractAjaxBehaviors found. A curious situation..."); } - for (Behavior b : behaviors) + for (AbstractAjaxTimerBehavior b : behaviors) { if (!(b instanceof AjaxSelfUpdatingTimerBehavior)) { @@ -78,6 +78,4 @@ public class AjaxLazyLoadPanelTester } }); } - - } http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/AjaxLazyLoadPanelTesterTest.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/AjaxLazyLoadPanelTesterTest.java b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/AjaxLazyLoadPanelTesterTest.java index 5ee746a..5cfa13a 100644 --- a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/AjaxLazyLoadPanelTesterTest.java +++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/AjaxLazyLoadPanelTesterTest.java @@ -43,7 +43,7 @@ public class AjaxLazyLoadPanelTesterTest extends WicketTestCase private static final long serialVersionUID = 1L; @Override - public Component getLazyLoadComponent(final String markupId) + public Component createContentComponent(final String markupId) { return new Label(markupId, "lazy panel test").setRenderBodyOnly(true); } http://git-wip-us.apache.org/repos/asf/wicket/blob/cfb75dd6/wicket-util/src/main/java/org/apache/wicket/util/value/LongValue.java ---------------------------------------------------------------------- diff --git a/wicket-util/src/main/java/org/apache/wicket/util/value/LongValue.java b/wicket-util/src/main/java/org/apache/wicket/util/value/LongValue.java index 8c96060..3e5e4bc 100755 --- a/wicket-util/src/main/java/org/apache/wicket/util/value/LongValue.java +++ b/wicket-util/src/main/java/org/apache/wicket/util/value/LongValue.java @@ -218,6 +218,27 @@ public class LongValue implements Comparable<LongValue>, Serializable } /** + * Returns the min of the two long values. + * + * @param <T> + * @param lhs + * @param rhs + * @throws IllegalArgumentException + * if either argument is {@code null} + * @return min value + */ + public static <T extends LongValue> T min(final T lhs, final T rhs) + { + Args.notNull(lhs, "lhs"); + Args.notNull(rhs, "rhs"); + if (lhs.compareTo(rhs) < 0) + { + return lhs; + } + return rhs; + } + + /** * Returns the max of the two long values. * * @param <T>