001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.codehaus.griffon.runtime.core;
019
020 import griffon.core.Context;
021 import griffon.core.ObservableContext;
022 import griffon.util.TypeUtils;
023
024 import javax.annotation.Nonnull;
025 import javax.annotation.Nullable;
026 import java.util.List;
027 import java.util.concurrent.CopyOnWriteArrayList;
028
029 import static java.util.Objects.requireNonNull;
030
031 /**
032 * @author Andres Almiray
033 * @since 2.5.0
034 */
035 public class DefaultObservableContext extends DefaultContext implements ObservableContext {
036 private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
037 private final List<ContextEventListener> listeners = new CopyOnWriteArrayList<>();
038
039 private final ContextEventListener parentListener = new ContextEventListener() {
040 @Override
041 public void contextChanged(@Nonnull ContextEvent event) {
042 String key = event.getKey();
043 if (!hasKey(key)) {
044 fireContextEvent(event.getType(), key, event.getOldValue(), event.getNewValue());
045 }
046 }
047 };
048
049 public DefaultObservableContext() {
050 super();
051 }
052
053 public DefaultObservableContext(@Nullable Context parentContext) {
054 super(parentContext);
055 if (parentContext instanceof ObservableContext) {
056 ObservableContext observableParent = (ObservableContext) parentContext;
057 observableParent.addContextEventListener(parentListener);
058 }
059 }
060
061 @Override
062 public void addContextEventListener(@Nonnull ContextEventListener listener) {
063 requireNonNull(listener, ERROR_LISTENER_NULL);
064 if (!listeners.contains(listener)) { listeners.add(listener); }
065 }
066
067 @Override
068 public void removeContextEventListener(@Nonnull ContextEventListener listener) {
069 requireNonNull(listener, ERROR_LISTENER_NULL);
070 listeners.remove(listener);
071 }
072
073 @Nonnull
074 @Override
075 public ContextEventListener[] getContextEventListeners() {
076 return listeners.toArray(new ContextEventListener[listeners.size()]);
077 }
078
079 @Override
080 public void put(@Nonnull String key, @Nullable Object value) {
081 boolean localKey = hasKey(key);
082 boolean parentKey = !localKey && containsKey(key);
083 Object oldValue = get(key);
084 super.put(key, value);
085 boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
086
087 if (parentKey) {
088 if (!valuesAreEqual) { fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value); }
089 } else {
090 if (localKey) {
091 fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
092 } else {
093 fireContextEvent(ContextEvent.Type.ADD, key, null, value);
094 }
095 }
096 }
097
098 @Nullable
099 @Override
100 public Object remove(@Nonnull String key) {
101 boolean localKey = hasKey(key);
102 Object oldValue = super.remove(key);
103 boolean localKeyRemoved = localKey && !hasKey(key);
104 boolean containsKey = containsKey(key);
105
106 try {
107 return oldValue;
108 } finally {
109 if (localKeyRemoved) {
110 if (containsKey) {
111 Object value = get(key);
112 boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
113 if (!valuesAreEqual) { fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value); }
114 } else {
115 fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
116 }
117 }
118 }
119 }
120
121 @Nullable
122 @Override
123 public <T> T removeAs(@Nonnull String key) {
124 boolean localKey = hasKey(key);
125 T oldValue = super.removeAs(key);
126 boolean localKeyRemoved = localKey && !hasKey(key);
127 boolean containsKey = containsKey(key);
128
129 try {
130 return oldValue;
131 } finally {
132 if (localKeyRemoved) {
133 if (containsKey) {
134 T value = getAs(key);
135 boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
136 if (!valuesAreEqual) { fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value); }
137 } else {
138 fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
139 }
140 }
141 }
142 }
143
144 @Nullable
145 @Override
146 public <T> T removeConverted(@Nonnull String key, @Nonnull Class<T> type) {
147 boolean localKey = hasKey(key);
148 T oldValue = super.removeConverted(key, type);
149 boolean localKeyRemoved = localKey && !hasKey(key);
150 boolean containsKey = containsKey(key);
151
152 try {
153 return oldValue;
154 } finally {
155 if (localKeyRemoved) {
156 if (containsKey) {
157 T value = getConverted(key, type);
158 boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
159 if (!valuesAreEqual) { fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value); }
160 } else {
161 fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
162 }
163 }
164 }
165 }
166
167 @Override
168 public void destroy() {
169 if (getParentContext() instanceof ObservableContext) {
170 ObservableContext observableParent = (ObservableContext) getParentContext();
171 observableParent.removeContextEventListener(parentListener);
172 }
173 listeners.clear();
174 super.destroy();
175 }
176
177 protected void fireContextEvent(@Nonnull ContextEvent.Type type, @Nonnull String key, @Nullable Object oldValue, @Nullable Object newValue) {
178 fireContextEvent(new ContextEvent(type, key, oldValue, newValue));
179 }
180
181 protected void fireContextEvent(@Nonnull ContextEvent event) {
182 for (ContextEventListener listener : listeners) {
183 listener.contextChanged(event);
184 }
185 }
186 }
|