001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  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,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.shared.asn1.codec.stateful;
021    
022    
023    import java.util.Stack;
024    
025    import org.apache.directory.shared.asn1.codec.DecoderException;
026    
027    
028    /**
029     * A stack of decoders used for the additive application of multiple decoders
030     * forming a linear staged decoder pipeline.
031     * 
032     * @author <a href="mailto:dev@directory.apache.org"> Apache Directory Project</a>
033     * @version $Rev: 664290 $
034     */
035    public class DecoderStack extends AbstractStatefulDecoder
036    {
037        /**
038         * the top decoder callback which calls this decoders callback
039         * 
040         * @todo determine if this is even necessary - can't we just use cb
041         */
042        private final DecoderCallback topcb;
043    
044        /** a stack of StatefulDecoders */
045        private Stack<StatefulDecoder> decoders = new Stack<StatefulDecoder>();
046    
047    
048        /**
049         * Creates an empty stack of chained decoders.
050         */
051        public DecoderStack()
052        {
053            topcb = new DecoderCallback()
054            {
055                public void decodeOccurred( StatefulDecoder decoder, Object decoded )
056                {
057                    DecoderStack.this.decodeOccurred( decoded );
058                }
059            };
060        }
061    
062    
063        /**
064         * Pushs a new terminal decoder onto the top of this DecoderStack. The old
065         * top decoder is chained to feed its decoded object to the new top decoder.
066         * The new pushed decoder will report decode events to this DecoderStacks
067         * callback.
068         * 
069         * @param decoder
070         *            the terminal decoder to push onto this stack
071         */
072        public synchronized void push( StatefulDecoder decoder )
073        {
074            decoder.setCallback( topcb );
075    
076            if ( !decoders.isEmpty() )
077            {
078                StatefulDecoder top = decoders.peek();
079                ChainingCallback chaining = new ChainingCallback( top, decoder );
080                top.setCallback( chaining );
081            }
082    
083            decoders.push( decoder );
084        }
085    
086    
087        /**
088         * Pops the terminal decoder off of this DecoderStack. The popped decoder
089         * has its callback cleared. If the stack is empty nothing happens and this
090         * StatefulDecoder, the DecoderStack, is returned to protect against null.
091         * 
092         * @return the top decoder that was popped, or this DecoderStack
093         */
094        public synchronized StatefulDecoder pop()
095        {
096            if ( decoders.isEmpty() )
097            {
098                return this;
099            }
100    
101            StatefulDecoder popped = decoders.pop();
102            popped.setCallback( null );
103    
104            if ( !decoders.isEmpty() )
105            {
106                StatefulDecoder top = decoders.peek();
107                top.setCallback( this.topcb );
108            }
109    
110            return popped;
111        }
112    
113    
114        /**
115         * Decodes an encoded object by calling decode on the decoder at the bottom
116         * of the stack. Callbacks are chained to feed the output of one decoder
117         * into the input decode method of another. If the stack is empty then the
118         * argument is delivered without change to this StatefulDecoder's callback.
119         * 
120         * @param encoded an object representing a piece of encoded data
121         * @throws DecoderException if the encoded element can't be decoded
122         */
123        public synchronized void decode( Object encoded ) throws DecoderException
124        {
125            if ( decoders.isEmpty() )
126            {
127                decodeOccurred( encoded );
128                return;
129            }
130    
131            decoders.get( 0 ).decode( encoded );
132        }
133    
134    
135        /**
136         * Gets whether or not this stack is empty.
137         * 
138         * @return true if the stack is empty, false otherwise
139         */
140        public boolean isEmpty()
141        {
142            return decoders.isEmpty();
143        }
144    
145    
146        /**
147         * Clears the stack popping all decoders setting their callbacks to null.
148         */
149        public synchronized void clear()
150        {
151            while ( !decoders.isEmpty() )
152            {
153                pop();
154            }
155        }
156    
157        /**
158         * A callback used to chain decoders.
159         * 
160         * @author <a href="mailto:dev@directory.apache.org"> Apache Directory
161         *         Project</a>
162         * @version $Rev: 664290 $
163         */
164        class ChainingCallback implements DecoderCallback
165        {
166            /** the source decoder calling this callback */
167            private StatefulDecoder sink;
168    
169            /** the sink decoder recieving the src's decoded object */
170            private StatefulDecoder src;
171    
172    
173            /**
174             * Creates a callback that chains the output of a src decoder to the
175             * input of a sink decoder. No side-effects occur like setting the
176             * callback of the src so this ChainingCallback must be set explicity as
177             * the src decoders callback.
178             * 
179             * @param src
180             *            the source decoder calling this callback
181             * @param sink
182             *            the sink decoder recieving the src's decoded object
183             */
184            ChainingCallback(StatefulDecoder src, StatefulDecoder sink)
185            {
186                this.src = src;
187                this.sink = sink;
188            }
189    
190    
191            /**
192             * Calls the {@link #decode(Object)} method of the sink if the decoder
193             * argument is the source. Any failures that occur during the sink's
194             * decode operation are reported to the monitor first then rethrown as
195             * runtime exceptions with the root cause set to the faulting exception.
196             * 
197             * @see org.apache.directory.shared.asn1.codec.stateful.DecoderCallback#decodeOccurred
198             *      (org.apache.directory.shared.asn1.codec.stateful.StatefulDecoder,
199             *      java.lang.Object)
200             */
201            public void decodeOccurred( StatefulDecoder decoder, Object decoded )
202            {
203                if ( decoder != src )
204                {
205                    return;
206                }
207    
208                try
209                {
210                    sink.decode( decoded );
211                }
212                catch ( DecoderException e )
213                {
214                    if ( getDecoderMonitor() != null )
215                    {
216                        getDecoderMonitor().fatalError( DecoderStack.this, e );
217                    }
218    
219                    throw new RuntimeException( e );
220                }
221            }
222        }
223    }