com.almworks.jira.structure.api.view.ViewSettings Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.view;
import com.almworks.jira.structure.api.settings.StructurePage;
import com.atlassian.annotations.PublicApi;
import org.codehaus.jackson.annotate.*;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.*;
import static com.almworks.jira.structure.api.settings.StructurePage.*;
/**
* View settings define a structure's parameters with regards to views - which
* views are associated with the structure and which views are default on which pages.
*
* {@code ViewSettings} class contains a list of {@link AssociatedView} records,
* with each record referencing a view by ID. Additionally, the record has "menu" markers,
* while define on which "structured" pages is the view offered to the user, and "default" markers,
* which define on which pages it is the default view.
*
* View settings can be set for a specific structure with {@link StructureViewManager#setViewSettings}.
* If per-structure view settings are not defined, then the global default view settings
* are in effect for that structure. Global view settings can be modified with
* {@link StructureViewManager#setDefaultViewSettings}.
*
* To get the current view settings for a structure, use {@link StructureViewManager#getViewSettings}.
* To construct a menu of views for the user, you would usually want to use {@link StructureViewManager#getMenuItems}.
*
* An instance of {@code ViewSettings} may be "undefined", which for the
* per-structure view settings means "use default".
*
* Note that views are referenced in {@code ViewSettings} by view ID. It is not guaranteed
* that a view with that ID exists, or that it is visible to the user. So before using the information
* from {@code ViewSettings}, check that the view is accessible.
*
* {@code ViewSettings} instance is immutable and thread-safe. To construct a new
* instance, use {@link Builder}.
*
* @author Igor Sereda
*/
@PublicApi
public class ViewSettings {
public static final Set NO_PAGES = Collections.emptySet();
public static final Set ALL_PAGES = Collections.unmodifiableSet(EnumSet.of(
STRUCTURE_BOARD, ISSUE_VIEW, PROJECT_TAB, GADGET));
public static final Set PAGES_WITH_DEFAULT_VIEW = Collections.unmodifiableSet(EnumSet.of(
STRUCTURE_BOARD, ISSUE_VIEW, PROJECT_TAB));
public static final ViewSettings EMPTY_SETTINGS = new Builder().build();
/**
* A list of associated views. A {@code null} value means "use default", while
* empty list means "no associated views".
*/
@Nullable
private final List myAssociatedViews;
private ViewSettings(@Nullable List associatedViews, boolean reuseList) {
myAssociatedViews = associatedViews == null ? null
: Collections.unmodifiableList(reuseList ? associatedViews : new ArrayList(associatedViews));
}
/**
* @return {@code true} if the view settings are defined, {@code false} means "use default settings"
*/
public boolean isDefined() {
return myAssociatedViews != null;
}
/**
* @return a list of associated views, or an empty list if settings are not defined
*/
@NotNull
public List getAssociatedViews() {
return myAssociatedViews == null ? Collections.emptyList() : myAssociatedViews;
}
/**
* Retrieves a default view ID for a given page. If any of the associated
* views has a view that is marked as the default for the specified page, the view ID
* is returned.
*
* @param page structure page for which default is needed
* @return default view ID, or null if the view settings do not define the default for that page
*/
@Nullable
public Long getDefaultViewForPage(StructurePage page) {
if (myAssociatedViews == null) return null;
for (AssociatedView view : myAssociatedViews) {
if (view.isDefault(page)) return view.getViewId();
}
return null;
}
/**
* Checks if the view settings contain a record for the specified view ID -
* that is, if the view is associated with the structure that is represented by this view settings instance.
*
* @param viewId the ID of the view
* @return true if this view settings instance includes the specified view
*/
public boolean hasView(Long viewId) {
if (viewId == null || myAssociatedViews == null) return false;
for (AssociatedView view : myAssociatedViews) {
if (view.getViewId() == viewId) return true;
}
return false;
}
private static StructurePage adjustPage(StructurePage page) {
if (page == COMPONENT_TAB || page == VERSION_TAB) return PROJECT_TAB;
if (ALL_PAGES.contains(page)) return page;
return STRUCTURE_BOARD;
}
public String toString() {
return "ViewSettings{" +
"associatedViews=" + myAssociatedViews +
'}';
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ViewSettings that = (ViewSettings) o;
if (myAssociatedViews != null ? !myAssociatedViews.equals(that.myAssociatedViews) : that.myAssociatedViews != null)
return false;
return true;
}
public int hashCode() {
return myAssociatedViews != null ? myAssociatedViews.hashCode() : 0;
}
/**
* View settings builder allows to construct instances of {@link ViewSettings} and serialize them
* into JSON format.
*
* Builder is not thread-safe.
*
* @see ViewSettings
*/
@XmlRootElement
public static class Builder {
private List myViews;
/**
* Creates an empty builder
*/
public Builder() {
}
/**
* Creates a builder that copies the contents of an already existing {@code ViewSettings} instance.
*
* @param copyFrom view settings to copy
*/
public Builder(@Nullable ViewSettings copyFrom) {
if (copyFrom != null && copyFrom.isDefined()) {
List copyViews = copyFrom.getAssociatedViews();
myViews = new ArrayList(copyViews.size());
for (AssociatedView view : copyViews) {
myViews.add(new AssociatedView.Builder(view));
}
}
}
/**
* @return a copy of list of {@link AssociatedView.Builder}, or {@code null} if no views have been added.
* Each individual builder is not copied but referenced in the resulting list.
*/
@Nullable
@SuppressWarnings("UnusedDeclaration")
@XmlElement
public List getViews() {
return myViews == null ? null : new ArrayList(myViews);
}
/**
* Sets a list of builders of associated views.
*
* @param views list of builders, or {@code null} to make the constructed view settings undefined.
*/
@SuppressWarnings("UnusedDeclaration")
public void setViews(List views) {
myViews = views == null ? null : new ArrayList(views);
}
/**
* Constructs an instance of {@link ViewSettings}. If a builder for any of the associated views
* is invalid, that associated view record is ignored.
*
* @return an instance of {@code ViewSettings}
*/
@NotNull
public ViewSettings build() {
List list = null;
if (myViews != null) {
list = new ArrayList(myViews.size());
for (AssociatedView.Builder view : myViews) {
AssociatedView v = view.build();
if (v != null) list.add(v);
}
}
return new ViewSettings(list, true);
}
/**
* Adds specified views to the list of associated views. Each view is available on
* every page ("menu" marker is set for all pages), and each view is not the default
* on any page ("default" marker is unset for all pages).
*
* @param viewIds a list of view IDs
* @return this builder
*/
public Builder addViews(long... viewIds) {
if (viewIds != null) {
for (long viewId : viewIds) {
addView(viewId, false);
}
}
return this;
}
/**
* Adds the specified view to the list of associated views, optionally making it the default
* for all pages. The view is made available on every page ("menu" marker is set for all pages),
* and if {@code defaultView} parameter is {@code true}, the view is also marked as the default
* for all pages ("default" marker is set for all pages).
*
* @param viewId view ID
* @param defaultView if {@code}, the added view will be the default
* @return this builder
*/
public Builder addView(long viewId, boolean defaultView) {
return addView(-1, viewId, null, defaultView ? PAGES_WITH_DEFAULT_VIEW : null);
}
/**
* Adds specified view to the list of associated views, or inserts it as a specific position in the list.
* The caller may specify on which pages the view is available in the drop-down list (via {@code menuPages}
* parameter), and on which pages the view is the default (via {@code defaultPages} parameter.
*
* Hint: to construct a collection of pages, use {@code EnumSet.of(...)}.
*
* @param index the position in the list where to add the view, or {@code -1} to add the view to the end of the list
* @param viewId the ID of the view
* @param menuPages a collection of pages on which this view is offered to the user, {@code null} or empty means all pages
* @param defaultPages a collection of pages on which this view is the default, {@code null} or empty means no pages
*
* @return this builder
* @throws IndexOutOfBoundsException if the specified index is invalid
*/
public Builder addView(int index, long viewId,
@Nullable Collection menuPages, @Nullable Collection defaultPages)
{
if (viewId <= 0) throw new IllegalArgumentException("cannot set parameters for view " + viewId);
makeDefined();
if (index < 0) {
index = myViews.size();
} else {
if (index > myViews.size()) throw new IndexOutOfBoundsException("bad index " + index);
}
for (int i = 0; i < myViews.size(); i++) {
AssociatedView.Builder builder = myViews.get(i);
if (builder.getViewId() == viewId) {
myViews.remove(i);
if (index > i) index--;
}
}
AssociatedView.Builder builder = new AssociatedView.Builder();
builder.setViewId(viewId);
builder.setMenuPages(enumSet(menuPages));
builder.setDefaultPages(enumSet(defaultPages));
myViews.add(index, builder);
return this;
}
/**
* Makes this view settings instance defined, by making sure the views list is not {@code null}.
* Even if you don't add any views after that and build an instance of {@link ViewSettings},
* the instance will be defined and override the default settings.
*/
public void makeDefined() {
if (myViews == null) myViews = new ArrayList(5);
}
/**
* @return true if the current state of the builder would create a defined instance of view settings
*/
@JsonIgnore
public boolean isDefined() {
return myViews != null;
}
public String toString() {
return "ViewSettings.Builder{" +
"views=" + myViews +
'}';
}
}
/**
* {@code AssociatedView} is a record of a view association within {@link ViewSettings}. A
* record contains a view ID and "menu" and "default" per-page markers that tell if the specified
* view is offered to the user in the menu on a page and if the view is the default on a page.
*
* The markers are represented as two sets of {@link StructurePage} - {@code menuPages}
* set contains pages on which the view is offered in drop-down, and {@code defaultPages}
* set contains pages on which the view is the default.
*
* This class is immutable and thread-safe. To construct a new instance, use
* {@link AssociatedView.Builder}.
*/
public static class AssociatedView {
private final long myViewId;
@NotNull
private final Set myMenuPages;
@NotNull
private final Set myDefaultPages;
private AssociatedView(long viewId, @Nullable Set menuPages,
@Nullable Set defaultPages)
{
myViewId = viewId;
myMenuPages = menuPages == null ? ALL_PAGES : Collections.unmodifiableSet(menuPages);
myDefaultPages = defaultPages == null ? NO_PAGES : Collections.unmodifiableSet(defaultPages);
}
/**
* @return unmodifiable set of pages which have this view in the drop-down
*/
@NotNull
public Set getMenuPages() {
return myMenuPages;
}
/**
* @return unmodifiable set of pages which have this view as the default
*/
@NotNull
public Set getDefaultPages() {
return myDefaultPages;
}
/**
* @return the view ID
*/
public long getViewId() {
return myViewId;
}
/**
* Checks if the view should be displayed in the menu on the specified page.
*
* @param page page with Structure widget
* @return true if the view should be offered in the drop-down
*/
public boolean isOnMenu(StructurePage page) {
return myMenuPages.contains(adjustPage(page));
}
/**
* Checks if the view is the default on the specified page.
*
* @param page page with Structure widget
* @return true if the view is the default
*/
public boolean isDefault(StructurePage page) {
return myDefaultPages.contains(adjustPage(page));
}
public String toString() {
return "ViewSettings.AssociatedView{" +
"viewId=" + myViewId +
", menuPages=" + myMenuPages +
", defaultPages=" + myDefaultPages +
'}';
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AssociatedView that = (AssociatedView) o;
if (myViewId != that.myViewId) return false;
if (!myDefaultPages.equals(that.myDefaultPages)) return false;
if (!myMenuPages.equals(that.myMenuPages)) return false;
return true;
}
public int hashCode() {
int result = (int) (myViewId ^ (myViewId >>> 32));
result = 31 * result + myMenuPages.hashCode();
result = 31 * result + myDefaultPages.hashCode();
return result;
}
/**
* The builder for {@link AssociatedView} record.
*/
@XmlRootElement
@JsonPropertyOrder({"view", "menuPages", "defaultPages"})
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public static class Builder {
private long myViewId;
private Set myMenuPages;
private Set myDefaultPages;
/**
* Creates an empty builder.
*/
public Builder() {
}
/**
* Creates a builder that copies the state of an already existing {@code AssociatedView} record.
*
* @param view view record to copy
*/
public Builder(AssociatedView view) {
if (view != null) {
myViewId = view.getViewId();
setMenuPages(view.getMenuPages());
setDefaultPages(view.getDefaultPages());
}
}
/**
* Creates an instance of {@link AssociatedView}.
*
* @return new instance of associated view record, or {@code null} if the builder is in invalid state
* (no view ID is set)
*/
public AssociatedView build() {
if (!isValid()) return null;
return new AssociatedView(myViewId, myMenuPages, myDefaultPages);
}
@JsonIgnore
private boolean isValid() {
return myViewId != 0;
}
/**
* @return set of pages on which the associated view will be in the menu, or {@code null} if
* the view should be in the menu on all pages.
*/
@Nullable
@XmlElement
public Set getMenuPages() {
return enumSet(myMenuPages);
}
/**
* Updates the set of pages on which the associated view will be in the menu.
*
* @param menuPages set of pages, on which the associated view should be in the Views drop-down menu. If {@code null}
* is passed, the view will be in the menu on all pages.
*/
public void setMenuPages(@Nullable Set menuPages) {
if (menuPages == null || menuPages.equals(ALL_PAGES)) {
myMenuPages = null;
} else {
myMenuPages = enumSet(menuPages);
}
replaceDetailsPage(myMenuPages);
validatePages(myMenuPages, ALL_PAGES);
}
/**
* @return set of pages on which the associated view will be the default, or {@code null} if
* the view should not be default on any page.
*/
@Nullable
@XmlElement
public Set getDefaultPages() {
return enumSet(myDefaultPages);
}
/**
* Updates the set of pages on which the associated view will be the default view.
*
* @param defaultPages set of pages, on which the associated view should be the default view. If {@code null}
* is passed, the view will not be the default on any page.
*/
public void setDefaultPages(@Nullable Set defaultPages) {
myDefaultPages = enumSet(defaultPages);
validatePages(myDefaultPages, PAGES_WITH_DEFAULT_VIEW);
}
/**
* @return the view ID
*/
@XmlElement
@JsonProperty("view")
public long getViewId() {
return myViewId;
}
/**
* Sets the ID of the view. No checks are made to make sure a view with such ID exists.
*
* @param viewId the id of the view
*/
public void setViewId(long viewId) {
myViewId = viewId;
}
private static void replaceDetailsPage(Set pages) {
if (pages != null && pages.remove(STRUCTURE_BOARD_WITH_DETAILS)) {
pages.addAll(EnumSet.of(STRUCTURE_BOARD, PROJECT_TAB));
}
}
private static void validatePages(Collection pages, Set universe) {
if (pages == null) return;
pages.retainAll(universe);
}
public String toString() {
return "ViewSettings.AssociatedView.Builder{" +
"viewId=" + myViewId +
", menuPages=" + myMenuPages +
", defaultPages=" + myDefaultPages +
'}';
}
}
}
}