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}