/* * Copyright (c) OSGi Alliance (2005, 2013). All Rights Reserved. * * 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.osgi.service.event; import static org.osgi.service.event.EventConstants.EVENT_TOPIC; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.List; import java.util.Map; import org.osgi.framework.Filter; /** * An event. * * {@code Event} objects are delivered to {@code EventHandler} services which * subscribe to the topic of the event. * * @Immutable * @author $Id: 6fc64b4e944ce1533562a5b1d79c2a19c8a78e76 $ */ public class Event { /** * The topic of this event. */ private final String topic; /** * The properties carried by this event. Keys are strings and values are * objects */ private final EventProperties properties; /** * Constructs an event. * * @param topic The topic of the event. * @param properties The event's properties (may be {@code null}). A * property whose key is not of type {@code String} will be ignored. * @throws IllegalArgumentException If topic is not a valid topic name. * @since 1.2 */ public Event(String topic, Map<String, ?> properties) { validateTopicName(topic); this.topic = topic; // safely publish the event properties this.properties = (properties instanceof EventProperties) ? (EventProperties) properties : new EventProperties(properties); } /** * Constructs an event. * * @param topic The topic of the event. * @param properties The event's properties (may be {@code null}). A * property whose key is not of type {@code String} will be ignored. * @throws IllegalArgumentException If topic is not a valid topic name. */ public Event(String topic, Dictionary<String, ?> properties) { validateTopicName(topic); this.topic = topic; // safely publish the event properties this.properties = new EventProperties(properties); } /** * Retrieve the value of an event property. The event topic may be retrieved * with the property name "event.topics". * * @param name The name of the property to retrieve. * @return The value of the property, or {@code null} if not found. */ public final Object getProperty(String name) { if (EVENT_TOPIC.equals(name)) { return topic; } return properties.get(name); } /** * Indicate the presence of an event property. The event topic is present * using the property name "event.topics". * * @param name The name of the property. * @return {@code true} if a property with the specified name is in the * event. This property may have a {@code null} value. {@code false} * otherwise. * @since 1.3 */ public final boolean containsProperty(String name) { if (EVENT_TOPIC.equals(name)) { return true; } return properties.containsKey(name); } /** * Returns a list of this event's property names. The list will include the * event topic property name "event.topics". * * @return A non-empty array with one element per property. */ public final String[] getPropertyNames() { int size = properties.size(); String[] result = new String[size + 1]; properties.keySet().toArray(result); result[size] = EVENT_TOPIC; return result; } /** * Returns the topic of this event. * * @return The topic of this event. */ public final String getTopic() { return topic; } /** * Tests this event's properties against the given filter using a case * sensitive match. * * @param filter The filter to test. * @return true If this event's properties match the filter, false * otherwise. */ public final boolean matches(Filter filter) { return filter.matchCase(new FilterProperties(topic, properties)); } /** * Compares this {@code Event} object to another object. * * <p> * An event is considered to be <b>equal to</b> another event if the topic * is equal and the properties are equal. The properties are compared using * the {@code java.util.Map.equals()} rules which includes identity * comparison for array values. * * @param object The {@code Event} object to be compared. * @return {@code true} if {@code object} is a {@code Event} and is equal to * this object; {@code false} otherwise. */ @Override public boolean equals(Object object) { if (object == this) { // quick test return true; } if (!(object instanceof Event)) { return false; } Event event = (Event) object; return topic.equals(event.topic) && properties.equals(event.properties); } /** * Returns a hash code value for this object. * * @return An integer which is a hash code value for this object. */ @Override public int hashCode() { int h = 31 * 17 + topic.hashCode(); h = 31 * h + properties.hashCode(); return h; } /** * Returns the string representation of this event. * * @return The string representation of this event. */ @Override public String toString() { return getClass().getName() + " [topic=" + topic + "]"; } /** * Called by the constructor to validate the topic name. * * @param topic The topic name to validate. * @throws IllegalArgumentException If the topic name is invalid. */ private static void validateTopicName(String topic) { char[] chars = topic.toCharArray(); int length = chars.length; if (length == 0) { throw new IllegalArgumentException("empty topic"); } for (int i = 0; i < length; i++) { char ch = chars[i]; if (ch == '/') { // Can't start or end with a '/' but anywhere else is okay if (i == 0 || (i == length - 1)) { throw new IllegalArgumentException("invalid topic: " + topic); } // Can't have "//" as that implies empty token if (chars[i - 1] == '/') { throw new IllegalArgumentException("invalid topic: " + topic); } continue; } if (('A' <= ch) && (ch <= 'Z')) { continue; } if (('a' <= ch) && (ch <= 'z')) { continue; } if (('0' <= ch) && (ch <= '9')) { continue; } if ((ch == '_') || (ch == '-')) { continue; } throw new IllegalArgumentException("invalid topic: " + topic); } } /** * Dictionary to use for Filter matching. */ static private final class FilterProperties extends Dictionary<String, Object> { private final String topic; private final EventProperties properties; FilterProperties(String topic, EventProperties properties) { this.topic = topic; this.properties = properties; } @Override public Enumeration<Object> elements() { Collection<Object> values = properties.values(); List<Object> result = new ArrayList<Object>(values.size() + 1); result.add(topic); result.addAll(values); return Collections.enumeration(result); } @Override public Object get(Object key) { if (EVENT_TOPIC.equals(key)) { return topic; } return properties.get(key); } @Override public boolean isEmpty() { return false; } @Override public Enumeration<String> keys() { Collection<String> keys = properties.keySet(); List<String> result = new ArrayList<String>(keys.size() + 1); result.add(EVENT_TOPIC); result.addAll(keys); return Collections.enumeration(result); } @Override public Object put(String key, Object value) { throw new UnsupportedOperationException(); } @Override public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public int size() { return properties.size() + 1; } } }