/*
Copyright 2014 Red Hat, Inc. and/or its affiliates.
This file is part of darcy-ui.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.redhat.darcy.ui;
import com.redhat.darcy.ui.annotations.Context;
import com.redhat.darcy.ui.annotations.NotRequired;
import com.redhat.darcy.ui.annotations.Require;
import com.redhat.darcy.ui.annotations.RequireAll;
import com.redhat.darcy.ui.api.ElementContext;
import com.redhat.darcy.ui.api.Transition;
import com.redhat.darcy.ui.api.View;
import com.redhat.darcy.ui.internal.Analyzer;
import com.redhat.darcy.ui.internal.Initializer;
import com.redhat.darcy.util.ReflectionUtil;
import com.redhat.synq.Condition;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* A partial implementation of View that initializes Element and View fields in
* {@link #setContext(com.redhat.darcy.ui.api.ElementContext)}, and simplifies defining load
* conditions (via {@link Require}, {@link RequireAll}, and {@link NotRequired}). This allows a
* client to succinctly and declaratively define a valid view simply by the content of that view's
* class fields and annotations.
*
* @see com.redhat.darcy.ui.Elements
* @see #setContext(com.redhat.darcy.ui.api.ElementContext)
* @see #onSetContext()
* @see #isLoaded()
*/
public abstract class AbstractView implements View {
/**
* The ElementContext for this View, managed by AbstractView.
*/
private ElementContext context;
/**
* Analyzes annotations and fields for load conditions. Intentionally package scope for
* collaboration with {@link com.redhat.darcy.ui.AbstractViewElement}.
*/
final Analyzer analyzer;
/**
* Sets context on fields that have a context whatever context is assigned to this view.
*/
private final Initializer initializer;
protected AbstractView() {
List<Field> declaredFields = ReflectionUtil.getAllDeclaredFields(this);
analyzer = new Analyzer(this, declaredFields);
initializer = new Initializer(this, declaredFields);
}
/**
* Determines whether or not the view is loaded by reading annotations
* ({@link com.redhat.darcy.ui.annotations.Require},
* {@link com.redhat.darcy.ui.annotations.RequireAll}, and
* {@link com.redhat.darcy.ui.annotations.NotRequired}), and all fields that
* implement one of {@link com.redhat.darcy.ui.api.View View},
* {@link com.redhat.darcy.ui.api.elements.Element Element}, or
* {@link com.redhat.darcy.ui.api.elements.Findable Findable}, or
* {@link java.util.List} of any of those types. Each field that is determined to be required
* will be queried based on its type, preferring {@link com.redhat.darcy.ui.api.View#isLoaded()}
* over {@link com.redhat.darcy.ui.api.elements.Element#isDisplayed() Element.isDisplayed()}
* over {@link com.redhat.darcy.ui.api.elements.Findable#isPresent() Findable.isPresent()}, and
* the combined success of these queries determines that this view is loaded.
*
* <p>Lists of those types are queried such that, by default, at least one element in the list
* is loaded, displayed, or present (in that order of precedence). The lists contents may change
* each time {@code isLoaded()} is called, reflecting whatever elements are found by the
* supplied locator at the time it is called. To require more than one element in a list, see
* {@link Require} documentation.
*
* <p>If no fields are configured to be required that implement one of those interfaces, a
* {@link com.redhat.darcy.ui.NoRequiredElementsException} will be thrown.
*
* @throws com.redhat.darcy.ui.NoRequiredElementsException if no
* {@link com.redhat.darcy.ui.api.View}, {@link com.redhat.darcy.ui.api.elements.Element},
* {@link com.redhat.darcy.ui.api.elements.Findable}, or
* {@link java.util.List} of those types is configured to be required.
*/
@Override
public boolean isLoaded() {
return analyzer.getLoadConditions().stream().allMatch(Condition::isMet);
}
/**
* In AbstractView, setContext triggers some helpful initializations:
* <ul>
* <li>If a field is annotated with {@link Context}, then the context parameter will be casted
* and assigned to that field. If the context does not implement that fields type, a
* {@link ClassCastException} will be thrown.</li>
* <li>If there are fields that implement {@link com.redhat.darcy.ui.api.HasElementContext},
* then they were created in such a way that they do not know about their owning View and,
* therefore, ElementContext. When setContext is called, LazyElements will get the context
* assigned to them.</li>
* <li>Calls {@link #onSetContext()} so that implementations of AbstractView may provide their
* own initializations that depend on the context, as necessary.</li>
* </ul>
*/
@Override
public final void setContext(ElementContext context) {
this.context = context;
initializer.initializeFields(context);
onSetContext();
}
@Override
public ElementContext getContext() {
return context;
}
/**
* Called after any call to {@link #setContext(ElementContext)}. Useful if you need to set up
* some fields that depend on this view having a context.
*/
protected void onSetContext() {
}
/**
* Shortcut for getContext().transition().
* @see ElementContext#transition()
*/
protected Transition transition() {
if (context == null) {
throw new NullContextException();
}
return context.transition();
}
}