summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/pagetemplates/model
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/main/java/com/yahoo/search/pagetemplates/model
Publish
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/pagetemplates/model')
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/AbstractChoice.java31
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Choice.java114
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Layout.java50
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/MapChoice.java69
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageElement.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageTemplateVisitor.java41
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Placeholder.java49
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Renderer.java88
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Section.java177
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/Source.java137
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/model/package-info.java7
11 files changed, 779 insertions, 0 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/AbstractChoice.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/AbstractChoice.java
new file mode 100644
index 00000000000..069598b2e02
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/AbstractChoice.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.component.provider.FreezableClass;
+
+/**
+ * Abstract superclass of various kinds of choices.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public abstract class AbstractChoice extends FreezableClass implements PageElement {
+
+ private String method;
+
+ /**
+ * Returns the choice method to use - a string interpreted by the resolver in use,
+ * or null to use any available method
+ */
+ public String getMethod() { return method; }
+
+ public void setMethod(String method) {
+ ensureNotFrozen();
+ this.method=method;
+ }
+
+ // TODO: is this really choices between classes in general, or e.g. subclasses of Section?
+ /** Returns true if this choice is (partially or completely) a choice between the given type */
+ @SuppressWarnings("rawtypes")
+ public abstract boolean isChoiceBetween(Class pageTemplateModelClass);
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Choice.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Choice.java
new file mode 100644
index 00000000000..a1932012236
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Choice.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import java.util.*;
+
+/**
+ * A choice between some alternative lists of page elements.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class Choice extends AbstractChoice {
+
+ private List<List<PageElement>> alternatives=new ArrayList<>(3);
+
+ /** Creates an empty choice */
+ public Choice() { }
+
+ /** Creates a choice having a single alternative having a single page element */
+ public static Choice createSingleton(PageElement singletonAlternative) {
+ Choice choice=new Choice();
+ choice.alternatives().add(createSingletonList(singletonAlternative));
+ return choice;
+ }
+
+ /** Creates a choice in which each alternative consists of a single element */
+ public static Choice createSingletons(List<PageElement> alternatives) {
+ Choice choice=new Choice();
+ for (PageElement alternative : alternatives)
+ choice.alternatives().add(createSingletonList(alternative));
+ return choice;
+ }
+
+ private static List<PageElement> createSingletonList(PageElement member) {
+ List<PageElement> list=new ArrayList<>();
+ list.add(member);
+ return list;
+ }
+
+ /**
+ * Creates a choice between some alternatives. This method takes a copy of the given lists.
+ */
+ public Choice(List<List<PageElement>> alternatives) {
+ for (List<PageElement> alternative : alternatives)
+ this.alternatives.add(new ArrayList<>(alternative));
+ }
+
+ /**
+ * Returns the alternatives of this as a live reference to the alternatives of this.
+ * The list and elements may be modified unless this is frozen. This is never null.
+ */
+ public List<List<PageElement>> alternatives() { return alternatives; }
+
+ /** Convenience shorthand of <code>return alternatives().get(index)</code> */
+ public List<PageElement> get(int index) {
+ return alternatives.get(index);
+ }
+
+ /** Convenience shorthand for <code>if (alternative!=null) alternatives().add(alternative)</code> */
+ public void add(List<PageElement> alternative) {
+ if (alternative!=null)
+ alternatives.add(new ArrayList<>(alternative));
+ }
+
+ /** Returns true only if there are no alternatives in this */
+ public boolean isEmpty() { return alternatives.size()==0; }
+
+ /** Answers true if this is either a choice between the given class, or between Lists of the given class */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public boolean isChoiceBetween(Class pageTemplateModelElementClass) {
+ List firstNonEmpty=null;
+ for (List<PageElement> value : alternatives) {
+ if (pageTemplateModelElementClass.isAssignableFrom(value.getClass())) return true;
+ if (value instanceof List) {
+ List listValue=(List)value;
+ if (listValue.size()>0)
+ firstNonEmpty=listValue;
+ }
+ }
+ if (firstNonEmpty==null) return false;
+ return (pageTemplateModelElementClass.isAssignableFrom(firstNonEmpty.get(0).getClass()));
+ }
+
+ @Override
+ public void freeze() {
+ if (isFrozen()) return;
+ super.freeze();
+ for (ListIterator<List<PageElement>> i=alternatives.listIterator(); i.hasNext(); ) {
+ List<PageElement> alternative=i.next();
+ for (PageElement alternativeElement : alternative)
+ alternativeElement.freeze();
+ i.set(Collections.unmodifiableList(alternative));
+ }
+ alternatives= Collections.unmodifiableList(alternatives);
+ }
+
+ /** Accepts a visitor to this structure */
+ @Override
+ public void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ for (List<PageElement> alternative : alternatives) {
+ for (PageElement alternativeElement : alternative)
+ alternativeElement.accept(visitor);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (alternatives.isEmpty()) return "(empty choice)";
+ if (alternatives.size()==1) return alternatives.get(0).toString();
+ return "a choice between " + alternatives;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Layout.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Layout.java
new file mode 100644
index 00000000000..f8e00b78787
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Layout.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+/**
+ * The layout of a section
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+// This is not made an enum, to allow the value set to be extendible.
+// It is not explicitly made immutable
+// to enable adding of internal state later (esp. parameters).
+// If this becomes mutable, the creation scheme must be changed
+// such that each fromString returns a unique instance, and
+// the name must become a (immutable) type.
+public class Layout {
+
+ /** The built in "column" layout */
+ public static final Layout column=new Layout("column");
+ /** The built in "row" layout */
+ public static final Layout row=new Layout("row");
+
+ private String name;
+
+ public Layout(String name) {
+ this.name=name;
+ }
+
+ public String getName() { return name; }
+
+ public @Override int hashCode() { return name.hashCode(); }
+
+ public @Override boolean equals(Object o) {
+ if (o==this) return true;
+ if (! (o instanceof Layout)) return false;
+ Layout other=(Layout)o;
+ return this.name.equals(other.name);
+ }
+
+ /** Returns a layout having this string as name, or null if the given string is null or empty */
+ public static Layout fromString(String layout) {
+ //if (layout==null) return null;
+ //if (layout)
+ if (layout.equals("column")) return column;
+ if (layout.equals("row")) return row;
+ return new Layout(layout);
+ }
+
+ public @Override String toString() { return "layout '" + name + "'"; }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/MapChoice.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/MapChoice.java
new file mode 100644
index 00000000000..33c3bba9a77
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/MapChoice.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A choice between different possible mapping functions of a set of values to a set of placeholder ids.
+ * A <i>resolution</i> of this choice consists of choosing a unique value for each placeholder id
+ * (hence a map choice is valid iff there are at least as many values as placeholder ids).
+ * <p>
+ * Each unique set of mappings (pairs) from values to placeholder ids is a separate possible
+ * alternative of this choice. The alternatives are not listed explicitly but are generated as needed.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MapChoice extends AbstractChoice {
+
+ private List<String> placeholderIds=new ArrayList<>();
+
+ private List<List<PageElement>> values=new ArrayList<>();
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public boolean isChoiceBetween(Class pageTemplateModelElementClass) {
+ List<PageElement> firstNonEmpty=null;
+ for (List<PageElement> value : values)
+ if (value.size()>0)
+ firstNonEmpty=value;
+ if (firstNonEmpty==null) return false;
+ return (pageTemplateModelElementClass.isAssignableFrom(firstNonEmpty.get(0).getClass()));
+ }
+
+ /**
+ * Returns the placeholder ids (the "to" of the mapping) of this as a live reference which can be modified unless
+ * this is frozen.
+ */
+ public List<String> placeholderIds() { return placeholderIds; }
+
+ /**
+ * Returns the values (the "from" of the mapping) of this as a live reference which can be modified unless
+ * this is frozen. Note that each single choice of values within this is also a list of values. This is
+ * the inner list.
+ */
+ public List<List<PageElement>> values() { return values; }
+
+ @Override
+ public void freeze() {
+ if (isFrozen()) return;
+ super.freeze();
+ placeholderIds=Collections.unmodifiableList(placeholderIds);
+ values=Collections.unmodifiableList(values);
+ }
+
+ /** Accepts a visitor to this structure */
+ public @Override void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ for (List<PageElement> valueEntry : values)
+ for (PageElement value : valueEntry)
+ value.accept(visitor);
+ }
+
+ @Override
+ public String toString() {
+ return "mapping to placeholders " + placeholderIds;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageElement.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageElement.java
new file mode 100644
index 00000000000..fba58f069ec
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageElement.java
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.component.provider.Freezable;
+
+/**
+ * Implemented by all page template model classes
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public interface PageElement extends Freezable {
+
+ /** Accepts a visitor to this structure */
+ public void accept(PageTemplateVisitor visitor);
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageTemplateVisitor.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageTemplateVisitor.java
new file mode 100644
index 00000000000..d7ebd3d1169
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/PageTemplateVisitor.java
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.search.pagetemplates.PageTemplate;
+
+/**
+ * Superclass of visitors over the page template object structure
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class PageTemplateVisitor {
+
+ /** Called each time a page template is encountered. This default implementation does nothing */
+ public void visit(PageTemplate pageTemplate) {
+ }
+
+ /** Called each time a source or source placeholder is encountered. This default implementation does nothing */
+ public void visit(Source source) {
+ }
+
+ /** Called each time a section or section placeholder is encountered. This default implementation does nothing */
+ public void visit(Section section) {
+ }
+
+ /** Called each time a renderer is encountered. This default implementation does nothing */
+ public void visit(Renderer renderer) {
+ }
+
+ /** Called each time a choice is encountered. This default implementation does nothing */
+ public void visit(Choice choice) {
+ }
+
+ /** Called each time a map choice is encountered. This default implementation does nothing */
+ public void visit(MapChoice choice) {
+ }
+
+ /** Called each time a placeholder is encountered. This default implementation does nothing */
+ public void visit(Placeholder placeholder) {
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Placeholder.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Placeholder.java
new file mode 100644
index 00000000000..cf7a85fc779
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Placeholder.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+/**
+ * A source placeholder is replaced with a list of source instances at evaluation time.
+ * Source placeholders may not have any content themselves - attempting to call any setter on this
+ * results in a IllegalStateException.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Placeholder implements PageElement {
+
+ private String id;
+
+ private MapChoice valueContainer=null;
+
+ /** Creates a source placeholder with an id. */
+ public Placeholder(String id) {
+ this.id=id;
+ }
+
+ public String getId() { return id; }
+
+ /** Returns the element which contains the value(s) of this placeholder. Never null. */
+ public MapChoice getValueContainer() { return valueContainer; }
+
+ public void setValueContainer(MapChoice valueContainer) { this.valueContainer=valueContainer; }
+
+ public @Override void freeze() {}
+
+ /** Accepts a visitor to this structure */
+ public @Override void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ public @Override String toString() {
+ return "source placeholder '" + id + "'";
+ }
+
+ /**
+ * This method always returns false, is a Placeholder always is mutable.
+ * (freeze() is a NOOP.)
+ */
+ @Override
+ public boolean isFrozen() {
+ return false;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Renderer.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Renderer.java
new file mode 100644
index 00000000000..4564ceeef3c
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Renderer.java
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.component.provider.FreezableClass;
+import com.yahoo.protect.Validator;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A description of a way to present data items from a source.
+ * All data items has a default renderer. This can be overridden or parametrized by
+ * an explicit renderer.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class Renderer extends FreezableClass implements PageElement {
+
+ private String name;
+
+ private String rendererFor;
+
+ private Map<String,String> parameters =new LinkedHashMap<>();
+
+ public Renderer(String name) {
+ setName(name);
+ }
+
+ /**
+ * Returns the name of this renderer (never null).
+ * The name should be recognized by the system receiving results for rendering
+ */
+ public String getName() { return name; }
+
+ public final void setName(String name) {
+ ensureNotFrozen();
+ Validator.ensureNotNull("renderer name",name);
+ this.name=name;
+ }
+
+ /**
+ * Returns the name of the kind of data this is a renderer for.
+ * This is used to allow frontends to dispatch the right data items (hits) to
+ * the right renderer in the case where the data consists of a heterogeneous list.
+ * <p>
+ * This is null if this is a renderer for a whole section, or if this is a renderer
+ * for all kinds of data from a particular source <i>and</i> this is not frozen.
+ * <p>
+ * Otherwise, it is either the name of the source this is the renderer for,
+ * <i>or</i> the renderer for all data items having this name as a <i>type</i>.
+ * <p>
+ * This, a (frontend) dispatcher of data to renderers should for each data item:
+ * <ul>
+ * <li>use the renderer having the same name as any <code>type</code> name set of the data item
+ * <li>if no such renderer, use the renderer having <code>rendererFor</code> equal to the data items <code>source</code>
+ * <li>if no such renderer, use a default renderer
+ * </ul>
+ */
+ public String getRendererFor() { return rendererFor; }
+
+ public void setRendererFor(String rendererFor) {
+ ensureNotFrozen();
+ this.rendererFor=rendererFor;
+ }
+
+ /**
+ * Returns the parameters of this renderer as a live reference (never null).
+ * The parameters will be passed to the renderer with each result
+ */
+ public Map<String,String> parameters() { return parameters; }
+
+ public @Override void freeze() {
+ if (isFrozen()) return;
+ super.freeze();
+ parameters = Collections.unmodifiableMap(parameters);
+ }
+
+ /** Accepts a visitor to this structure */
+ public @Override void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ }
+ public @Override String toString() {
+ return "renderer '" + name + "'";
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Section.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Section.java
new file mode 100644
index 00000000000..0a980419853
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Section.java
@@ -0,0 +1,177 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.component.provider.FreezableClass;
+import com.yahoo.search.query.Sorting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An element of a page template corresponding to a physical area of the layout of the final physical page.
+ * Pages are freezable - once frozen calling a setter will cause an IllegalStateException, and returned
+ * live collection references are unmodifiable
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Section extends FreezableClass implements PageElement {
+
+ private final String id;
+
+ private Layout layout=Layout.column;
+
+ private String region;
+
+ /** The elements of this - sources, subsections etc. and/or choices of the same */
+ private List<PageElement> elements=new ArrayList<>();
+
+ /** Filtered versions of elements pre-calculated at freeze time */
+ private List<PageElement> sections, sources, renderers;
+
+ private int max=-1;
+
+ private int min=-1;
+
+ private Sorting order=null;
+
+ private static AtomicInteger nextId=new AtomicInteger();
+
+ public Section() {
+ this(null);
+ }
+
+ /** Creates a section with an id (or null if no id) */
+ public Section(String id) {
+ if (id==null || id.isEmpty())
+ this.id=String.valueOf("section_" + nextId.incrementAndGet());
+ else
+ this.id=id;
+ }
+
+ /** Returns a unique id of this section within the page. Used for referencing and identification. Never null. */
+ public String getId() { return id; }
+
+ /**
+ * Returns the layout identifier describing the kind of layout which should be used by the rendering engine to
+ * lay out the content of this section. This is never null. Default: "column".
+ */
+ public Layout getLayout() { return layout; }
+
+ /** Sets the layout. If the layout is set to null it will become Layout.column */
+ public void setLayout(Layout layout) {
+ ensureNotFrozen();
+ if (layout==null) layout=Layout.column;
+ this.layout=layout;
+ }
+
+ /**
+ * Returns the identifier telling the layout of the containing section where this section should be placed.
+ * Permissible values, and whether this is mandatory is determined by the particular layout identifier of the parent.
+ * May be null if a placement is not required by the containing layout, or if this is the top-level section.
+ * This is null by default.
+ */
+ public String getRegion() { return region; }
+
+ public void setRegion(String region) {
+ ensureNotFrozen();
+ this.region=region;
+ }
+
+ /**
+ * Returns the elements of this - sources, subsections and presentations and/or choices of these,
+ * as a live reference which can be modified to change the content of this (unless this is frozen).
+ * <p>
+ * All elements are kept in a single list to allow multiple elements of each type to be nested within separate
+ * choices, and to maintain the internal order of elements of various types, which is sometimes significant.
+ * To extract a certain kind of elements (say, sources), the element list must be traversed to collect
+ * all source elements as well as all choices of sources.
+ * <p>
+ * This list is never null but may be empty.
+ */
+ public List<PageElement> elements() { return elements; }
+
+ /**
+ * Convenience method which returns the elements <b>and choices</b> of the given type in elements as a
+ * read-only list. Not that as this returns both concrete elements and choices betwen them,
+ * the list element cannot be case to the given class - this must be used in conjunction
+ * with a resolve which contains the resolution to the choices.
+ *
+ * @param pageTemplateModelElementClass type to returns elements and choices of, a subtype of PageElement
+ */
+ public List<PageElement> elements(@SuppressWarnings("rawtypes") Class pageTemplateModelElementClass) {
+ if (isFrozen()) { // Use precalculated lists
+ if (pageTemplateModelElementClass==Section.class)
+ return sections;
+ else if (pageTemplateModelElementClass==Source.class)
+ return sources;
+ else if (pageTemplateModelElementClass==Renderer.class)
+ return renderers;
+ }
+ return createElementList(pageTemplateModelElementClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<PageElement> createElementList(@SuppressWarnings("rawtypes") Class pageTemplateModelElementClass) {
+ List<PageElement> filteredElements=new ArrayList<>();
+ for (PageElement element : elements) {
+ if (pageTemplateModelElementClass.isAssignableFrom(element.getClass()))
+ filteredElements.add(element);
+ else if (element instanceof AbstractChoice)
+ if (((AbstractChoice)element).isChoiceBetween(pageTemplateModelElementClass))
+ filteredElements.add(element);
+ }
+ return Collections.unmodifiableList(filteredElements);
+ }
+
+ /** Returns the choice of ways to sort immediate children in this, or empty meaning sort by default order (relevance) */
+ public Sorting getOrder() { return order; }
+
+ public void setOrder(Sorting order) {
+ ensureNotFrozen();
+ this.order=order;
+ }
+
+ /** Returns max number of (immediate) elements/sections permissible within this, -1 means unrestricted. Default: -1. */
+ public int getMax() { return max; }
+
+ public void setMax(int max) {
+ ensureNotFrozen();
+ this.max=max;
+ }
+
+ /** Returns min number of (immediate) elements/sections desired within this, -1 means unrestricted. Default: -1. */
+ public int getMin() { return min; }
+
+ public void setMin(int min) {
+ ensureNotFrozen();
+ this.min=min;
+ }
+
+ public @Override void freeze() {
+ if (isFrozen()) return;
+
+ for (PageElement element : elements)
+ element.freeze();
+ elements=Collections.unmodifiableList(elements);
+ sections=createElementList(Section.class);
+ sources=createElementList(Source.class);
+ renderers=createElementList(Renderer.class);
+
+ super.freeze();
+ }
+
+ /** Accepts a visitor to this structure */
+ public @Override void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ for (PageElement element : elements)
+ element.accept(visitor);
+ }
+
+ public @Override String toString() {
+ if (id==null || id.isEmpty()) return "a section";
+ return "section '" + id + "'";
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Source.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Source.java
new file mode 100644
index 00000000000..91c403eae84
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/Source.java
@@ -0,0 +1,137 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.component.provider.FreezableClass;
+import com.yahoo.protect.Validator;
+
+import java.util.*;
+
+/**
+ * A source mentioned in a page template.
+ * <p>
+ * Two sources are equal if they have the same name and parameters.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Source extends FreezableClass implements PageElement {
+
+ /** The "any" source - used to mark that any source is acceptable here */
+ public static final Source any=new Source("*",true);
+
+ /** The obligatory name of a source */
+ private String name;
+
+ private List<PageElement> renderers =new ArrayList<>();
+
+ private Map<String,String> parameters =new LinkedHashMap<>();
+
+ private String url;
+
+ /** The precalculated hashCode of this object, or 0 if this is not frozen */
+ private int hashCode=0;
+
+ public Source(String name) {
+ this(name,false);
+ }
+
+ /** Creates a source and optionally immediately freezes it */
+ private Source(String name,boolean freeze) {
+ setName(name);
+ if (freeze)
+ freeze();
+ }
+
+ /** Returns the name of this source (never null) */
+ public String getName() { return name; }
+
+ public final void setName(String name) {
+ ensureNotFrozen();
+ Validator.ensureNotNull("Source name",name);
+ this.name=name;
+ }
+
+ /** Returns the url of this source or null if none */
+ public String getUrl() { return url; }
+
+ /**
+ * Sets the url of this source. If a source has an url (i.e this returns non-null), the content of
+ * the url is <i>not</i> fetched - fetching is left to the frontend by exposing this url in the result.
+ */
+ public void setUrl(String url) {
+ ensureNotFrozen();
+ this.url=url;
+ }
+
+ /**
+ * Returns the renderers or choices of renderers to apply on individual items of this source
+ * <p>
+ * If this contains multiple renderers/choices, they are to be used on different types of hits returned by this source.
+ */
+ public List<PageElement> renderers() { return renderers; }
+
+ /**
+ * Returns the parameters of this source as a live reference (never null).
+ * The parameters will be passed to the provider getting source data.
+ */
+ public Map<String,String> parameters() { return parameters; }
+
+ public @Override void freeze() {
+ if (isFrozen()) return;
+ for (PageElement element : renderers) {
+ if (element instanceof Renderer) {
+ assignRendererForIfNotSet((Renderer)element);
+ }
+ else if (element instanceof Choice) {
+ for (List<PageElement> renderersAlternative : ((Choice)element).alternatives()) {
+ for (PageElement rendererElement : renderersAlternative) {
+ Renderer renderer=(Renderer)rendererElement;
+ if (renderer.getRendererFor()==null)
+ renderer.setRendererFor(name);
+ }
+ }
+ }
+ element.freeze();
+ }
+ parameters = Collections.unmodifiableMap(parameters);
+ hashCode=hashCode();
+ super.freeze();
+ }
+
+ private void assignRendererForIfNotSet(Renderer renderer) {
+ if (renderer.getRendererFor()==null)
+ renderer.setRendererFor(name);
+ }
+
+ /** Accepts a visitor to this structure */
+ public @Override void accept(PageTemplateVisitor visitor) {
+ visitor.visit(this);
+ for (PageElement renderer : renderers)
+ renderer.accept(visitor);
+ }
+
+ public @Override int hashCode() {
+ if (isFrozen()) return hashCode;
+ int hashCode=name.hashCode();
+ int i=0;
+ for (Map.Entry<String,String> parameter : parameters.entrySet())
+ hashCode+=i*17*parameter.getKey().hashCode()+i*31*parameter.getValue().hashCode();
+ return hashCode;
+ }
+
+ public @Override boolean equals(Object other) {
+ if (other==this) return true;
+ if (! (other instanceof Source)) return false;
+ Source otherSource=(Source)other;
+ if (! this.name.equals(otherSource.name)) return false;
+ if (this.parameters.size() != otherSource.parameters.size()) return false;
+ for (Map.Entry<String,String> thisParameter : this.parameters.entrySet())
+ if ( ! thisParameter.getValue().equals(otherSource.parameters.get(thisParameter.getKey())))
+ return false;
+ return true;
+ }
+
+ public @Override String toString() {
+ return "source '" + name + "'";
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/model/package-info.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/package-info.java
new file mode 100644
index 00000000000..22a004d7555
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/model/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.search.pagetemplates.model;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;