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}