001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.tree; 018 019import java.util.Collection; 020import java.util.List; 021import java.util.concurrent.atomic.AtomicBoolean; 022 023/** 024 * <p> 025 * A specialized {@code NodeModel} implementation that uses a tracked node managed by an {@link InMemoryNodeModel} 026 * object as root node. 027 * </p> 028 * <p> 029 * Models of this type are useful when working on specific sub trees of a nodes structure. This is the case for instance 030 * for a {@code SubnodeConfiguration}. 031 * </p> 032 * <p> 033 * An instance of this class is constructed with an {@link InMemoryNodeModelSupport} object providing a reference to the 034 * underlying {@code InMemoryNodeModel} and the {@link NodeSelector} pointing to the tracked node acting as this model's 035 * root node. The {@code NodeModel} operations are implemented by delegating to the wrapped {@code InMemoryNodeModel} 036 * object specifying the selector to the tracked node as target root node for the update transaction. Note that the 037 * tracked node can become detached at any time. This situation is handled transparently by the implementation of 038 * {@code InMemoryNodeModel}. The reason for using an {@code InMemoryNodeModelSupport} object rather than an 039 * {@code InMemoryNodeModel} directly is that this additional layer of indirection can be used for performing special 040 * initializations on the model before it is returned to the {@code TrackedNodeModel} object. This is needed by some 041 * dynamic configuration implementations, for example by {@code CombinedConfiguration}. 042 * </p> 043 * <p> 044 * If the tracked node acting as root node is exclusively used by this model, it should be released when this model is 045 * no longer needed. This can be done manually by calling the {@link #close()} method. It is also possible to pass a 046 * value of <strong>true</strong> to the {@code untrackOnFinalize} argument of the constructor. This causes 047 * {@code close()} to be called automatically if this object gets claimed by the garbage collector. 048 * </p> 049 * <p> 050 * As {@code InMemoryNodeModel}, this class is thread-safe. 051 * </p> 052 * 053 * @since 2.0 054 */ 055public class TrackedNodeModel implements NodeModel<ImmutableNode> { 056 057 /** Stores the underlying parent model. */ 058 private final InMemoryNodeModelSupport parentModelSupport; 059 060 /** The selector for the managed tracked node. */ 061 private final NodeSelector selector; 062 063 /** 064 * A flag whether the tracked not should be released when this object is finalized. 065 */ 066 private final boolean releaseTrackedNodeOnFinalize; 067 068 /** A flag whether this model has already been closed. */ 069 private final AtomicBoolean closed; 070 071 /** 072 * Creates a new instance of {@code TrackedNodeModel} and initializes it with the given underlying model and the 073 * selector to the root node. The boolean argument controls whether the associated tracked node should be released when 074 * this object gets finalized. This allows the underlying model to free some resources. If used as model within a 075 * {@code SubnodeConfiguration}, there is typically no way to discard the model explicitly. Therefore, it makes sense to 076 * do this automatically on finalization. 077 * 078 * @param modelSupport the underlying {@code InMemoryNodeModelSupport} (must not be <strong>null</strong>) 079 * @param sel the selector to the root node of this model (must not be <strong>null</strong>) 080 * @param untrackOnFinalize a flag whether the tracked node should be released on finalization 081 * @throws IllegalArgumentException if a required parameter is missing 082 */ 083 public TrackedNodeModel(final InMemoryNodeModelSupport modelSupport, final NodeSelector sel, final boolean untrackOnFinalize) { 084 if (modelSupport == null) { 085 throw new IllegalArgumentException("Underlying model support must not be null!"); 086 } 087 if (sel == null) { 088 throw new IllegalArgumentException("Selector must not be null!"); 089 } 090 091 parentModelSupport = modelSupport; 092 selector = sel; 093 releaseTrackedNodeOnFinalize = untrackOnFinalize; 094 closed = new AtomicBoolean(); 095 } 096 097 @Override 098 public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, final NodeKeyResolver<ImmutableNode> resolver) { 099 getParentModel().addNodes(key, getSelector(), nodes, resolver); 100 } 101 102 @Override 103 public void addProperty(final String key, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) { 104 getParentModel().addProperty(key, getSelector(), values, resolver); 105 } 106 107 /** 108 * {@inheritDoc} This implementation clears the sub tree spanned by the associate tracked node. This has the side effect 109 * that this in any case becomes detached. 110 * 111 * @param resolver the {@code NodeKeyResolver}. 112 */ 113 @Override 114 public void clear(final NodeKeyResolver<ImmutableNode> resolver) { 115 getParentModel().clearTree(null, getSelector(), resolver); 116 } 117 118 @Override 119 public void clearProperty(final String key, final NodeKeyResolver<ImmutableNode> resolver) { 120 getParentModel().clearProperty(key, getSelector(), resolver); 121 } 122 123 @Override 124 public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeKeyResolver<ImmutableNode> resolver) { 125 return getParentModel().clearTree(key, getSelector(), resolver); 126 } 127 128 /** 129 * Closes this model. This causes the tracked node this model is based upon to be released (i.e. 130 * {@link InMemoryNodeModel#untrackNode(NodeSelector)} is called). This method should be called when this model is no 131 * longer needed. This implementation is idempotent; it is safe to call {@code close()} multiple times - only the first 132 * invocation has an effect. After this method has been called this model can no longer be used because there is no 133 * guarantee that the node can still be accessed from the parent model. 134 */ 135 public void close() { 136 if (closed.compareAndSet(false, true)) { 137 getParentModel().untrackNode(getSelector()); 138 } 139 } 140 141 /** 142 * {@inheritDoc} This implementation calls {@code close()} if the {@code untrackOnFinalize} flag was set when this 143 * instance was constructed. While this is not 100 percent reliable, it is better than keeping the tracked node hanging 144 * around. Note that it is not a problem if {@code close()} already had been invoked manually because this method is 145 * idempotent. 146 * 147 * @see #close() 148 */ 149 @Override 150 protected void finalize() throws Throwable { 151 if (isReleaseTrackedNodeOnFinalize()) { 152 close(); 153 } 154 super.finalize(); 155 } 156 157 /** 158 * {@inheritDoc} This implementation returns the tracked node instance acting as root node of this model. 159 */ 160 @Override 161 public ImmutableNode getInMemoryRepresentation() { 162 return getNodeHandler().getRootNode(); 163 } 164 165 @Override 166 public NodeHandler<ImmutableNode> getNodeHandler() { 167 return getParentModel().getTrackedNodeHandler(getSelector()); 168 } 169 170 /** 171 * Gets the parent model. Operations on this model are delegated to this parent model specifying the selector to the 172 * tracked node. 173 * 174 * @return the parent model 175 */ 176 public InMemoryNodeModel getParentModel() { 177 return getParentModelSupport().getNodeModel(); 178 } 179 180 /** 181 * Gets the {@code InMemoryNodeModelSupport} object which is used to gain access to the underlying node model. 182 * 183 * @return the associated {@code InMemoryNodeModelSupport} object 184 */ 185 public InMemoryNodeModelSupport getParentModelSupport() { 186 return parentModelSupport; 187 } 188 189 /** 190 * Gets the {@code NodeSelector} pointing to the tracked node managed by this model. 191 * 192 * @return the tracked node selector 193 */ 194 public NodeSelector getSelector() { 195 return selector; 196 } 197 198 /** 199 * Returns the flag whether the managed tracked node is to be released when this object gets finalized. This method 200 * returns the value of the corresponding flag passed to the constructor. If result is true, the underlying model is 201 * asked to untrack the managed node when this object is claimed by the GC. 202 * 203 * @return a flag whether the managed tracked node should be released when this object dies 204 * @see InMemoryNodeModel#untrackNode(NodeSelector) 205 */ 206 public boolean isReleaseTrackedNodeOnFinalize() { 207 return releaseTrackedNodeOnFinalize; 208 } 209 210 @Override 211 public void setProperty(final String key, final Object value, final NodeKeyResolver<ImmutableNode> resolver) { 212 getParentModel().setProperty(key, getSelector(), value, resolver); 213 } 214 215 @Override 216 public void setRootNode(final ImmutableNode newRoot) { 217 getParentModel().replaceTrackedNode(getSelector(), newRoot); 218 } 219}