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