001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.console.freemarker; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022import java.util.stream.*; 023 024import org.apache.juneau.commons.http.*; 025import org.apache.juneau.marshall.collections.*; 026 027/** 028 * The built-in {@code markers} decorator: vertical mark lines at x values from a list, a same-origin {@code src}, a 029 * {@code data:} topic, or the current time. The lines ride a carrier series {@code juneau-markers-<n>}. 030 * 031 * <h5 class='section'>Example:</h5> 032 * <p class='bjava'> 033 * Markers.<jsm>from</jsm>(<js>"/rest/releases"</js>).at(<js>"date"</js>).label(<js>"name"</js>).onClick(Topics.<jsm>custom</jsm>(<js>"app"</js>, <js>"release"</js>)); 034 * Markers.<jsm>now</jsm>(<js>"Today"</js>); 035 * </p> 036 * 037 * @since 10.0.0 038 */ 039public final class Markers implements Decorator { 040 041 private static final Set<String> STYLES = Set.of("solid", "dashed", "dotted"); 042 043 private final JsonMap m = JsonMap.of("use", "markers"); 044 045 private Markers() {} 046 047 /** @param path A same-origin path returning rows. @return A new decorator. */ 048 public static Markers from(String path) { 049 if (! SafePathTemplate.isSameOriginPath(path)) 050 throw iaex("Markers src must be a same-origin path; got '%s'.", path); 051 var x = new Markers(); 052 x.m.put("src", path); 053 return x; 054 } 055 056 /** @param rows Inline rows {@code {at, label?, meta?}}. @return A new decorator. */ 057 public static Markers values(List<? extends Map<String,?>> rows) { 058 var x = new Markers(); 059 x.m.put("values", copyRows(rows)); 060 return x; 061 } 062 063 /** @param dataTopic A {@code data:} topic ({@code Topics.data(...)}). @return A new decorator. */ 064 public static Markers source(String dataTopic) { 065 var x = new Markers(); 066 x.m.put("source", dataTopicOf("Markers", dataTopic)); 067 return x; 068 } 069 070 /** @param label The label of a mark line at the current time. @return A new decorator. */ 071 public static Markers now(String label) { 072 var x = new Markers(); 073 x.m.put("now", JsonMap.of("label", label)); 074 return x; 075 } 076 077 /** @param field The row field holding the x value. @return This object. */ 078 public Markers at(String field) { m.put("at", field); return this; } 079 080 /** @param field The row field holding the line label. @return This object. */ 081 public Markers label(String field) { m.put("label", field); return this; } 082 083 /** @param v {@code solid}, {@code dashed} or {@code dotted}. @return This object. */ 084 public Markers style(String v) { 085 if (! STYLES.contains(v)) 086 throw iaex("Markers style '%s' must be 'solid', 'dashed' or 'dotted'.", v); 087 m.put("style", v); 088 return this; 089 } 090 091 /** @param topic A custom topic the card publishes a click on a line to. @return This object. */ 092 public Markers onClick(String topic) { m.put("topic", topic); return this; } 093 094 @Override 095 public JsonMap toJson() { return new JsonMap(m); } 096 097 static List<Map<String,Object>> copyRows(List<? extends Map<String,?>> rows) { 098 if (rows == null) 099 throw iaex("Decorator values must not be null."); 100 return rows.stream().<Map<String,Object>>map(LinkedHashMap::new).collect(Collectors.toCollection(ArrayList::new)); 101 } 102 103 static String dataTopicOf(String who, String topic) { 104 if (topic == null || ! topic.startsWith("data:") || topic.length() == 5) 105 throw iaex("%s source must be a data: topic; got '%s'.", who, topic); 106 return topic; 107 } 108}