DefaultObservableContext.java
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 = (ObservableContextparentContext;
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 = (ObservableContextgetParentContext();
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 }