1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.flatfile;
18
19 import java.io.InputStream;
20 import java.lang.reflect.InvocationHandler;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27
28 import org.apache.commons.lang3.ClassUtils;
29
30 /**
31 * Immutable Entity factory, using Java 1.3 proxies.
32 * @version $Revision: 1301232 $ $Date: 2012-03-15 17:03:25 -0500 (Thu, 15 Mar 2012) $
33 */
34 public class ImmutableEntity {
35 private static class ImmutableEntityInvocationHandler implements InvocationHandler {
36 // make sure this array stays sorted:
37 private static final String[] ALLOWED_VOID = { "readFrom", "writeTo" };
38 private static final String CLONE = "clone";
39 private static final String EQUALS = "equals";
40 private static final String READ_FROM = "readFrom";
41 private static final String GET_CHILD = "getChild";
42 private static final String GET_VALUE = "getValue";
43 private static final String IS_SIZABLE = "isSizable";
44 private static final byte[] READ_BUFFER = new byte[1024];
45 private static final int BUF_LEN = READ_BUFFER.length;
46
47 private Entity delegate;
48
49 /**
50 * Create a new ImmutableEntityInvocationHandler instance.
51 * @param delegate Entity
52 */
53 private ImmutableEntityInvocationHandler(Entity delegate) {
54 this.delegate = delegate;
55 }
56
57 /**
58 * {@inheritDoc}
59 */
60 // TODO could we simplify/future-proof proxy definition by breaking Entity into smaller interfaces?
61 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
62 String name = method.getName();
63
64 // check allowed void methods; most would be intended to trigger a
65 // change on the target
66 if (method.getReturnType() == Void.TYPE && Arrays.binarySearch(ALLOWED_VOID, name) < 0) {
67 return null;
68 }
69 if (READ_FROM.equals(name)) {
70 InputStream is = (InputStream) args[0];
71 int remaining = delegate.length();
72 while (remaining > 0) {
73 int toread = remaining > BUF_LEN ? BUF_LEN : remaining;
74 int read = is.read(READ_BUFFER, 0, toread);
75 if (read < 0) {
76 break;
77 }
78 remaining -= read;
79 }
80 return null;
81 }
82 if (CLONE.equals(name)) {
83 // it should be safe to return the proxy itself as the clone of
84 // an IMMUTABLE entity
85 return proxy;
86 }
87 if (EQUALS.equals(name)) {
88 return Boolean.valueOf(proxy == args[0]);
89 }
90 if (IS_SIZABLE.equals(name)) {
91 return Boolean.FALSE;
92 }
93 Object result = method.invoke(delegate, args);
94 if (GET_CHILD.equals(name)) { // return immutable children
95 result = of((Entity) result);
96 }
97 // if delegate returns same result 2x, that indicates it's the true
98 // buffer, which we protect by copying:
99 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 }