// Copyright Vespa.ai. 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. *

* Two sources are equal if they have the same name and parameters. * * @author bratseth */ 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 renderers =new ArrayList<>(); private Map 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 not 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 *

* If this contains multiple renderers/choices, they are to be used on different types of hits returned by this source. */ public List 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 parameters() { return parameters; } @Override public void freeze() { if (isFrozen()) return; for (PageElement element : renderers) { if (element instanceof Renderer) { assignRendererForIfNotSet((Renderer)element); } else if (element instanceof Choice) { for (List 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 */ @Override public void accept(PageTemplateVisitor visitor) { visitor.visit(this); for (PageElement renderer : renderers) renderer.accept(visitor); } @Override public int hashCode() { if (isFrozen()) return hashCode; int hashCode=name.hashCode(); int i=0; for (Map.Entry parameter : parameters.entrySet()) hashCode+=i*17*parameter.getKey().hashCode()+i*31*parameter.getValue().hashCode(); return hashCode; } @Override public 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 thisParameter : this.parameters.entrySet()) if ( ! thisParameter.getValue().equals(otherSource.parameters.get(thisParameter.getKey()))) return false; return true; } @Override public String toString() { return "source '" + name + "'"; } }