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 */
019package org.apache.maven.doxia.sink.impl;
020
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Proxy;
025import java.util.LinkedList;
026import java.util.Queue;
027
028import org.apache.maven.doxia.sink.Sink;
029
030/**
031 * Buffers all method calls on the proxied Sink until its {@link Sink#flush()} is called.
032 */
033public class BufferingSinkProxyFactory implements SinkWrapperFactory {
034
035    private static final class MethodWithArguments {
036        private final Method method;
037        private final Object[] args;
038
039        MethodWithArguments(Method method, Object[] args) {
040            super();
041            this.method = method;
042            this.args = args;
043        }
044
045        void invoke(Object object) {
046            try {
047                method.invoke(object, args);
048            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
049                throw new IllegalStateException("Could not call buffered method " + method, e);
050            }
051        }
052    }
053
054    public interface BufferingSink extends Sink {
055        // just a marker interface
056        Sink getBufferedSink();
057    }
058
059    private static final class BufferingSinkProxy implements InvocationHandler {
060        private final Queue<MethodWithArguments> bufferedInvocations;
061        private final Sink delegate;
062        private static final Method FLUSH_METHOD;
063        private static final Method GET_BUFFERED_SINK_METHOD;
064        private static final Method GET_DOCUMENT_LOCATOR_METHOD;
065
066        static {
067            try {
068                FLUSH_METHOD = Sink.class.getMethod("flush");
069                GET_BUFFERED_SINK_METHOD = BufferingSink.class.getMethod("getBufferedSink");
070                GET_DOCUMENT_LOCATOR_METHOD = BufferingSink.class.getMethod("getDocumentLocator");
071            } catch (NoSuchMethodException | SecurityException e) {
072                throw new IllegalStateException("Could not find flush method in Sink!", e);
073            }
074        }
075
076        BufferingSinkProxy(Sink delegate) {
077            bufferedInvocations = new LinkedList<>();
078            this.delegate = delegate;
079        }
080
081        @Override
082        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
083            if (method.equals(FLUSH_METHOD)) {
084                bufferedInvocations.forEach(i -> i.invoke(delegate));
085                bufferedInvocations.clear();
086            } else if (method.equals(GET_BUFFERED_SINK_METHOD)) {
087                return delegate;
088            } else if (method.equals(GET_DOCUMENT_LOCATOR_METHOD)) {
089                return delegate.getDocumentLocator();
090            } else {
091                bufferedInvocations.add(new MethodWithArguments(method, args));
092            }
093            if (method.getReturnType() != Void.TYPE) {
094                throw new IllegalStateException(
095                        "BufferingSinkProxy only works for methods returning void, but given method " + method
096                                + " requires another return type");
097            }
098            return null;
099        }
100    }
101
102    @Override
103    public Sink createWrapper(Sink delegate) {
104        BufferingSinkProxy proxy = new BufferingSinkProxy(delegate);
105        return (Sink) Proxy.newProxyInstance(
106                delegate.getClass().getClassLoader(), new Class<?>[] {BufferingSink.class}, proxy);
107    }
108
109    public static BufferingSink castAsBufferingSink(Sink sink) {
110        if (sink instanceof BufferingSink) {
111            return (BufferingSink) sink;
112        } else {
113            throw new IllegalArgumentException("The given sink is not a BufferingSink but a " + sink.getClass());
114        }
115    }
116
117    @Override
118    public int getPriority() {
119        return 0;
120    }
121}