All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cometd.bayeux.ChannelId Maven / Gradle / Ivy

There is a newer version: 4.0.9
Show newest version
/*
 * Copyright (c) 2008-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cometd.bayeux;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 

Reification of a {@link Channel#getId() channel id} with methods to test properties * and compare with other {@link ChannelId}s.

*

A {@link ChannelId} breaks the channel id into path segments so that, for example, * {@code /foo/bar} breaks into {@code ["foo","bar"]}.

*

{@link ChannelId} can be wild, when they end with one or two wild characters {@code "*"}; * a {@link ChannelId} is shallow wild if it ends with one wild character (for example {@code /foo/bar/*}) * and deep wild if it ends with two wild characters (for example {@code /foo/bar/**}).

*

{@link ChannelId} can be a template, when a segment contains variable names surrounded by * braces, for example {@code /foo/{var_name}}. Variable names can only be made of characters * defined by the {@link Pattern \w} regular expression character class.

*/ public class ChannelId { public static final String WILD = "*"; public static final String DEEPWILD = "**"; private static final Pattern VAR = Pattern.compile("\\{(\\w+)\\}"); private final String _id; private String[] _segments; private int _wild; private List _wilds; private String _parent; private List _vars; /** * Constructs a new {@code ChannelId} with the given id * * @param id the channel id in string form */ public ChannelId(String id) { if (id == null || id.length() == 0 || id.charAt(0) != '/' || "/".equals(id)) throw new IllegalArgumentException("Invalid channel id: " + id); id = id.trim(); if (id.charAt(id.length() - 1) == '/') id = id.substring(0, id.length() - 1); _id = id; } private void resolve() { synchronized (this) { if (_segments != null) return; resolve(_id); } } private void resolve(String name) { String[] segments = name.substring(1).split("/"); if (segments.length < 1) throw new IllegalArgumentException("Invalid channel id: " + this); for (int i = 1, size = segments.length; i <= size; ++i) { String segment = segments[i - 1]; if (i < size && (WILD.equals(segment) || DEEPWILD.equals(segment))) throw new IllegalArgumentException("Invalid channel id: " + this); Matcher matcher = VAR.matcher(segment); if (matcher.matches()) { if (_vars == null) _vars = new ArrayList<>(); _vars.add(matcher.group(1)); } if (i == size) _wild = DEEPWILD.equals(segment) ? 2 : WILD.equals(segment) ? 1 : 0; } if (_vars == null) _vars = Collections.emptyList(); else _vars = Collections.unmodifiableList(_vars); if (_wild > 0) { if (!_vars.isEmpty()) throw new IllegalArgumentException("Invalid channel id: " + this); _wilds = Collections.emptyList(); } else { boolean addShallow = true; List wilds = new ArrayList<>(segments.length + 1); StringBuilder b = new StringBuilder(name.length()).append("/"); for (int i = 1, size = segments.length; i <= size; ++i) { String segment = segments[i - 1]; if (segment.trim().length() == 0) throw new IllegalArgumentException("Invalid channel id: " + this); wilds.add(0, b + "**"); if (segment.matches(VAR.pattern())) { addShallow = i == size; break; } if (i < size) b.append(segment).append('/'); } if (addShallow) wilds.add(0, b + "*"); _wilds = Collections.unmodifiableList(wilds); } _parent = segments.length == 1 ? null : name.substring(0, name.length() - segments[segments.length - 1].length() - 1); _segments = segments; } /** * @return whether this {@code ChannelId} is either {@link #isShallowWild() shallow wild} * or {@link #isDeepWild() deep wild} */ public boolean isWild() { resolve(); return _wild > 0; } /** *

Shallow wild {@code ChannelId}s end with a single wild character {@code "*"} * and {@link #matches(ChannelId) match} non wild channels with * the same {@link #depth() depth}.

*

Example: {@code /foo/*} matches {@code /foo/bar}, but not {@code /foo/bar/baz}.

* * @return whether this {@code ChannelId} is a shallow wild channel id */ public boolean isShallowWild() { return isWild() && !isDeepWild(); } /** *

Deep wild {@code ChannelId}s end with a double wild character "**" * and {@link #matches(ChannelId) match} non wild channels with * the same or greater {@link #depth() depth}.

*

Example: {@code /foo/**} matches {@code /foo/bar} and {@code /foo/bar/baz}.

* * @return whether this {@code ChannelId} is a deep wild channel id */ public boolean isDeepWild() { resolve(); return _wild > 1; } /** *

A {@code ChannelId} is a meta {@code ChannelId} if it starts with {@code "/meta/"}.

* * @return whether the first segment is "meta" */ public boolean isMeta() { return isMeta(_id); } /** *

A {@code ChannelId} is a service {@code ChannelId} if it starts with {@code "/service/"}.

* * @return whether the first segment is "service" */ public boolean isService() { return isService(_id); } /** * @return whether this {@code ChannelId} is neither {@link #isMeta() meta} nor {@link #isService() service} */ public boolean isBroadcast() { return isBroadcast(_id); } /** * @return whether this {@code ChannelId} is a template, that is it contains segments * that identify a variable name between braces, such as {@code /foo/{var_name}}. * * @see #bind(ChannelId) * @see #getParameters() */ public boolean isTemplate() { resolve(); return !_vars.isEmpty(); } /** * @return the list of variable names if this {@link ChannelId} is a template, * otherwise an empty list. * @see #isTemplate() */ public List getParameters() { resolve(); return _vars; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ChannelId)) return false; ChannelId that = (ChannelId)obj; return _id.equals(that._id); } @Override public int hashCode() { return _id.hashCode(); } /** *

Tests whether this {@code ChannelId} matches the given {@code ChannelId}.

*

If the given {@code ChannelId} is {@link #isWild() wild}, * then it matches only if it is equal to this {@code ChannelId}.

*

If this {@code ChannelId} is non-wild, * then it matches only if it is equal to the given {@code ChannelId}.

*

Otherwise, this {@code ChannelId} is either shallow or deep wild, and * matches {@code ChannelId}s with the same number of equal segments (if it is * shallow wild), or {@code ChannelId}s with the same or a greater number of * equal segments (if it is deep wild).

* * @param channelId the channelId to match * @return true if this {@code ChannelId} matches the given {@code ChannelId} */ public boolean matches(ChannelId channelId) { resolve(); if (channelId.isWild()) return equals(channelId); switch (_wild) { case 0: { return equals(channelId); } case 1: { if (channelId._segments.length != _segments.length) return false; for (int i = _segments.length - 1; i-- > 0; ) if (!_segments[i].equals(channelId._segments[i])) return false; return true; } case 2: { if (channelId._segments.length < _segments.length) return false; for (int i = _segments.length - 1; i-- > 0; ) if (!_segments[i].equals(channelId._segments[i])) return false; return true; } default: { throw new IllegalStateException(); } } } /** *

If this {@code ChannelId} is a template, and the given {@code target} {@code ChannelId} * is non-wild and non-template, and the two have the same {@link #depth()}, then binds * the variable(s) defined in this template with the values of the segments defined by * the target {@code ChannelId}.

*

For example:

*
     * // template and target match.
     * Map<String, String> bindings = new ChannelId("/a/{var1}/c/{var2}").bind(new ChannelId("/a/foo/c/bar"));
     * bindings: {"var1": "foo", "var2": "bar"}
     *
     * // template has 2 segments, target has only 1 segment.
     * bindings = new ChannelId("/a/{var1}").bind(new ChannelId("/a"))
     * bindings = {}
     *
     * // template has 2 segments, target too many segments.
     * bindings = new ChannelId("/a/{var1}").bind(new ChannelId("/a/b/c"))
     * bindings = {}
     *
     * // same number of segments, but no match on non-variable segments.
     * bindings = new ChannelId("/a/{var1}").bind(new ChannelId("/b/c"))
     * bindings = {}
     * 
*

The returned map may not preserve the order of variables present in the template {@code ChannelId}.

* * @param target the non-wild, non-template {@code ChannelId} to bind * @return a map withe the bindings, or an empty map if no binding was possible * @see #isTemplate() */ public Map bind(ChannelId target) { if (!isTemplate() || target.isTemplate() || target.isWild() || depth() != target.depth()) return Collections.emptyMap(); Map result = new LinkedHashMap<>(); for (int i = 0; i < _segments.length; ++i) { String thisSegment = getSegment(i); String thatSegment = target.getSegment(i); Matcher matcher = VAR.matcher(thisSegment); if (matcher.matches()) { result.put(matcher.group(1), thatSegment); } else { if (!thisSegment.equals(thatSegment)) return Collections.emptyMap(); } } return result; } @Override public String toString() { return _id; } /** * @return how many segments this {@code ChannelId} is made of * @see #getSegment(int) */ public int depth() { resolve(); return _segments.length; } /** * @param id the channel to test * @return whether this {@code ChannelId} is an ancestor of the given {@code ChannelId} * @see #isParentOf(ChannelId) */ public boolean isAncestorOf(ChannelId id) { resolve(); if (isWild() || depth() >= id.depth()) return false; for (int i = _segments.length; i-- > 0; ) { if (!_segments[i].equals(id._segments[i])) return false; } return true; } /** * @param id the channel to test * @return whether this {@code ChannelId} is the parent of the given {@code ChannelId} * @see #isAncestorOf(ChannelId) */ public boolean isParentOf(ChannelId id) { resolve(); if (isWild() || depth() != id.depth() - 1) return false; for (int i = _segments.length; i-- > 0; ) { if (!_segments[i].equals(id._segments[i])) return false; } return true; } /** * @return the channel string parent of this {@code ChannelId}, * or null if this {@code ChannelId} has only one segment * @see #isParentOf(ChannelId) */ public String getParent() { resolve(); return _parent; } /** * @param i the segment index * @return the i-nth segment of this channel, or null if no such segment exist * @see #depth() */ public String getSegment(int i) { resolve(); if (i >= _segments.length) return null; return _segments[i]; } /** * @return The list of wilds channels that match this channel, or * the empty list if this channel is already wild. */ public List getWilds() { resolve(); return _wilds; } /** *

Returns the regular part of this {@link ChannelId}: the part * of the channel id from the beginning until the first occurrence * of a parameter or a wild character.

*

Examples:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ChannelId.regularPart Examples
ChannelRegular Part
/foo/foo
/foo/*/foo
/foo/bar/**/foo/bar
/foo/{p}/foo
/foo/bar/{p}/foo/bar
/*null
/**null
/{p}null
* * @return the regular part of this channel */ public String getRegularPart() { resolve(); if (isWild()) return getParent(); if (!isTemplate()) return _id; int regular = depth() - getParameters().size(); if (regular <= 0) return null; String result = ""; for (int i = 0; i < regular; ++i) result += "/" + getSegment(i); return result; } /** *

Helper method to test if the string form of a {@code ChannelId} * represents a {@link #isMeta() meta} {@code ChannelId}.

* * @param channelId the channel id to test * @return whether the given channel id is a meta channel id */ public static boolean isMeta(String channelId) { return channelId != null && channelId.startsWith("/meta/"); } /** *

Helper method to test if the string form of a {@code ChannelId} * represents a {@link #isService() service} {@code ChannelId}.

* * @param channelId the channel id to test * @return whether the given channel id is a service channel id */ public static boolean isService(String channelId) { return channelId != null && channelId.startsWith("/service/"); } /** *

Helper method to test if the string form of a {@code ChannelId} * represents a {@link #isBroadcast() broadcast} {@code ChannelId}.

* * @param channelId the channel id to test * @return whether the given channel id is a broadcast channel id */ public static boolean isBroadcast(String channelId) { return !isMeta(channelId) && !isService(channelId); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy