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; 018 019import java.util.*; 020import java.util.regex.*; 021 022/** 023 * A reference to a client-side handler: a registered name or an opt-in inline body. 024 * 025 * <p> 026 * Names use the {@code ns.name} grammar and are registered on the page with {@code JuneauViews.handlers.register}. 027 * The {@code juneau.*} namespace is reserved for the built-ins (see {@link #builtin(String)}). 028 * 029 * <h2 class='section'>Example:</h2> 030 * <p class='bjava'> 031 * HandlerRef <jv>a</jv> = HandlerRef.<jsm>named</jsm>(<js>"orders.refresh"</js>); 032 * HandlerRef <jv>b</jv> = HandlerRef.<jsm>builtin</jsm>(<js>"refresh"</js>); <jc>// juneau.refresh</jc> 033 * </p> 034 * 035 * @since 10.0.0 036 */ 037@SuppressWarnings({ 038 "java:S1845", // Instance field name and the NAME pattern constant are distinct, conventionally named members. 039 "java:S5998" // Handler names are short, so the regex repetition depth is bounded. 040}) 041public final class HandlerRef { 042 043 private static final Pattern NAME = Pattern.compile("^[a-z][A-Za-z0-9]*(\\.[a-z][A-Za-z0-9-]*)+$"); 044 045 /** The built-in handler names, without the {@code juneau.} prefix. */ 046 static final Set<String> BUILTINS = Set.of("refresh", "refreshClosest", "expand", "collapse", "activate", "publish", "countLabel", "countBadge"); 047 048 private final String name; // null when inline 049 private final String inline; // null when named 050 051 private HandlerRef(String name, String inline) { 052 this.name = name; 053 this.inline = inline; 054 } 055 056 /** 057 * A registered handler name. 058 * 059 * @param name The name, e.g. {@code orders.refresh}. A {@code juneau.}-prefixed name is accepted only when it names a built-in. 060 * @return A new reference. 061 * @throws IllegalArgumentException If the name is malformed or uses the reserved namespace. 062 */ 063 public static HandlerRef named(String name) { 064 if (name == null || ! NAME.matcher(name).matches()) 065 throw new IllegalArgumentException("Handler name '" + name + "' must match ns.name (lower-case first letter per segment, at least one dot)."); 066 if (name.startsWith("juneau.") && ! BUILTINS.contains(name.substring(7))) 067 throw new IllegalArgumentException("Handler name '" + name + "' uses the reserved juneau.* namespace."); 068 return new HandlerRef(name, null); 069 } 070 071 /** 072 * A built-in handler. 073 * 074 * @param name The built-in name without prefix, e.g. {@code refresh}. 075 * @return A reference to {@code juneau.<name>}. 076 * @throws IllegalArgumentException If the name is not a built-in. 077 */ 078 public static HandlerRef builtin(String name) { 079 if (! BUILTINS.contains(name)) 080 throw new IllegalArgumentException("'" + name + "' is not a built-in handler: " + new TreeSet<>(BUILTINS)); 081 return new HandlerRef("juneau." + name, null); 082 } 083 084 /** 085 * An inline handler body, compiled on the client as {@code function(target, event)}. 086 * Needs {@code PageSpec.allowInlineHandlers(true)}. 087 * 088 * @param body The function body. 089 * @return A new reference. 090 * @throws IllegalArgumentException If the body is blank. 091 */ 092 public static HandlerRef inline(String body) { 093 if (body == null || body.isBlank()) 094 throw new IllegalArgumentException("Inline handler body must not be empty."); 095 return new HandlerRef(null, body); 096 } 097 098 /** 099 * Returns the handler name. 100 * 101 * @return The name, or <jk>null</jk> for an inline reference. 102 */ 103 public String name() { 104 return name; 105 } 106 107 /** 108 * Returns whether this is an inline body. 109 * 110 * @return <jk>true</jk> if inline. 111 */ 112 public boolean isInline() { 113 return inline != null; 114 } 115 116 /** 117 * Returns the contract JSON form. 118 * 119 * @return The name string, or {@code {"inline": body}}. 120 */ 121 public Object toJson() { 122 return inline != null ? Map.of("inline", inline) : name; 123 } 124 125 @Override /* Object */ 126 public boolean equals(Object o) { 127 return o instanceof HandlerRef r && Objects.equals(name, r.name) && Objects.equals(inline, r.inline); 128 } 129 130 @Override /* Object */ 131 public int hashCode() { 132 return Objects.hash(name, inline); 133 } 134 135 @Override /* Object */ 136 public String toString() { 137 return inline != null ? "inline:" + inline : name; 138 } 139}