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 * http://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.flatfile; 018 019import java.io.InputStream; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.util.Arrays; 024import java.util.List; 025import java.util.Map; 026import java.util.WeakHashMap; 027 028import org.apache.commons.lang3.ClassUtils; 029 030/** 031 * Immutable Entity factory, using Java 1.3 proxies. 032 * @version $Revision: 1301232 $ $Date: 2012-03-15 17:03:25 -0500 (Thu, 15 Mar 2012) $ 033 */ 034public class ImmutableEntity { 035 private static class ImmutableEntityInvocationHandler implements InvocationHandler { 036 // make sure this array stays sorted: 037 private static final String[] ALLOWED_VOID = { "readFrom", "writeTo" }; 038 private static final String CLONE = "clone"; 039 private static final String EQUALS = "equals"; 040 private static final String READ_FROM = "readFrom"; 041 private static final String GET_CHILD = "getChild"; 042 private static final String GET_VALUE = "getValue"; 043 private static final String IS_SIZABLE = "isSizable"; 044 private static final byte[] READ_BUFFER = new byte[1024]; 045 private static final int BUF_LEN = READ_BUFFER.length; 046 047 private Entity delegate; 048 049 /** 050 * Create a new ImmutableEntityInvocationHandler instance. 051 * @param delegate Entity 052 */ 053 private ImmutableEntityInvocationHandler(Entity delegate) { 054 this.delegate = delegate; 055 } 056 057 /** 058 * {@inheritDoc} 059 */ 060 // TODO could we simplify/future-proof proxy definition by breaking Entity into smaller interfaces? 061 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 062 String name = method.getName(); 063 064 // check allowed void methods; most would be intended to trigger a 065 // change on the target 066 if (method.getReturnType() == Void.TYPE && Arrays.binarySearch(ALLOWED_VOID, name) < 0) { 067 return null; 068 } 069 if (READ_FROM.equals(name)) { 070 InputStream is = (InputStream) args[0]; 071 int remaining = delegate.length(); 072 while (remaining > 0) { 073 int toread = remaining > BUF_LEN ? BUF_LEN : remaining; 074 int read = is.read(READ_BUFFER, 0, toread); 075 if (read < 0) { 076 break; 077 } 078 remaining -= read; 079 } 080 return null; 081 } 082 if (CLONE.equals(name)) { 083 // it should be safe to return the proxy itself as the clone of 084 // an IMMUTABLE entity 085 return proxy; 086 } 087 if (EQUALS.equals(name)) { 088 return Boolean.valueOf(proxy == args[0]); 089 } 090 if (IS_SIZABLE.equals(name)) { 091 return Boolean.FALSE; 092 } 093 Object result = method.invoke(delegate, args); 094 if (GET_CHILD.equals(name)) { // return immutable children 095 result = of((Entity) result); 096 } 097 // if delegate returns same result 2x, that indicates it's the true 098 // buffer, which we protect by copying: 099 if (GET_VALUE.equals(name)) { 100 byte[] test = (byte[]) method.invoke(delegate, args); 101 if (result == test) { 102 result = new byte[test.length]; 103 System.arraycopy(test, 0, (byte[]) result, 0, test.length); 104 } 105 } 106 return result; 107 } 108 } 109 110 private static final Map<Entity, Entity> PROXIES = new WeakHashMap<Entity, Entity>(); 111 112 /** 113 * Create a new ImmutableEntity instance. 114 */ 115 private ImmutableEntity() { 116 } 117 118 /** 119 * Get an immutable instance of <code>e</code>. 120 * @param e Entity delegate 121 * @return immutable Entity 122 */ 123 public static Entity of(Entity e) { 124 Entity result = PROXIES.get(e); 125 if (result == null) { 126 result = (Entity) Proxy.newProxyInstance(e.getClass().getClassLoader(), 127 getInterfaces(e), new ImmutableEntityInvocationHandler(e)); 128 PROXIES.put(e, result); 129 } 130 return result; 131 } 132 133 /** 134 * Get the interfaces implemented by <code>o</code>. 135 * @param o to inspect. 136 * @return interface array 137 */ 138 private static Class<?>[] getInterfaces(Object o) { 139 final List<Class<?>> interfaces = ClassUtils.getAllInterfaces(o.getClass()); 140 return interfaces.toArray(new Class[interfaces.size()]); 141 } 142}