View Javadoc

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 }