| /******************************************************************************* |
| * Copyright (c) 2012, 2015 Obeo. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Obeo - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.ide.internal.utils; |
| |
| import com.google.common.cache.CacheBuilder; |
| import com.google.common.cache.CacheLoader; |
| import com.google.common.cache.LoadingCache; |
| import com.google.common.collect.Maps; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.Map; |
| import java.util.concurrent.ExecutionException; |
| |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.xmi.XMIException; |
| import org.eclipse.emf.ecore.xmi.XMLHelper; |
| import org.eclipse.emf.ecore.xmi.XMLResource; |
| import org.eclipse.emf.ecore.xmi.impl.XMLHandler; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| /** |
| * This implementation of an {@link XMLHandler} will forward all calls to its delegate. |
| * |
| * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> |
| */ |
| public class ForwardingXMLHandler extends XMLHandler { |
| /** |
| * The cache in which we'll store the methods accessed through reflection. Note that this is most likely |
| * never used. |
| */ |
| private static final Map<String, Method> METHOD_CACHE = Maps.newHashMap(); |
| |
| /** |
| * The cache in which we'll store the fields we access through reflection. Note that this is only used by |
| * a single field in this implementation. |
| */ |
| private static final LoadingCache<String, Field> FIELD_CACHE = CacheBuilder.newBuilder() |
| .build(new CacheLoader<String, Field>() { |
| /** |
| * {@inheritDoc} |
| * |
| * @see com.google.common.cache.CacheLoader#load(java.lang.Object) |
| */ |
| @Override |
| public Field load(String key) throws Exception { |
| final Field field = findField(key); |
| // Make it accessible right off the bat |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| public Object run() { |
| field.setAccessible(true); |
| return null; |
| } |
| }); |
| return field; |
| } |
| }); |
| |
| /** The delegate to which we'll forward all calls. */ |
| protected final XMLHandler delegate; |
| |
| /** |
| * Creates this forwarding handler given its delegate. All other parameters are only used to call the |
| * mandatory super-constructor... but none should be of any use here. |
| * |
| * @param delegate |
| * Our delegate XMLHandler. |
| * @param xmlResource |
| * The resource we'll be loading. Mandatory for the super-constructor, but we'll forward all |
| * calls to {@code delegate} anyway. |
| * @param helper |
| * The xml helper to use. Mandatory for the super-constructor, but we'll forward all calls to |
| * {@code delegate} anyway. |
| * @param options |
| * The load options that were specified. Mandatory for the super-constructor, but we'll forward |
| * all calls to {@code delegate} anyway. |
| */ |
| public ForwardingXMLHandler(XMLHandler delegate, XMLResource xmlResource, XMLHelper helper, |
| Map<?, ?> options) { |
| super(xmlResource, helper, options); |
| this.delegate = delegate; |
| } |
| |
| /** |
| * Returns the delegate instance that methods are forwarded to. |
| * |
| * @return The delegate instance that methods are forwarded to. |
| */ |
| protected XMLHandler delegate() { |
| return this.delegate; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endDocument() |
| */ |
| @Override |
| public void endDocument() { |
| delegate().endDocument(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#characters(char[], int, int) |
| */ |
| @Override |
| public void characters(char[] ch, int start, int length) { |
| delegate().characters(ch, start, length); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#comment(char[], int, int) |
| */ |
| @Override |
| public void comment(char[] ch, int start, int length) { |
| delegate().comment(ch, start, length); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endCDATA() |
| */ |
| @Override |
| public void endCDATA() { |
| delegate().endCDATA(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endDTD() |
| */ |
| @Override |
| public void endDTD() { |
| delegate().endDTD(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endElement(java.lang.String, java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public void endElement(String uri, String localName, String name) { |
| delegate().endElement(uri, localName, name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endEntity(java.lang.String) |
| */ |
| @Override |
| public void endEntity(String name) { |
| delegate().endEntity(name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#endPrefixMapping(java.lang.String) |
| */ |
| @Override |
| public void endPrefixMapping(String prefix) { |
| delegate().endPrefixMapping(prefix); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| return delegate().equals(obj); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#error(org.xml.sax.SAXParseException) |
| */ |
| @Override |
| public void error(SAXParseException e) throws SAXException { |
| delegate().error(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#error(org.eclipse.emf.ecore.xmi.XMIException) |
| */ |
| @Override |
| public void error(XMIException e) { |
| delegate().error(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#fatalError(org.xml.sax.SAXParseException) |
| */ |
| @Override |
| public void fatalError(SAXParseException e) throws SAXException { |
| delegate().fatalError(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#fatalError(org.eclipse.emf.ecore.xmi.XMIException) |
| */ |
| @Override |
| public void fatalError(XMIException e) { |
| delegate().fatalError(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.lang.Object#hashCode() |
| */ |
| @Override |
| public int hashCode() { |
| return delegate().hashCode(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#ignorableWhitespace(char[], int, int) |
| */ |
| @Override |
| public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { |
| delegate().ignorableWhitespace(ch, start, length); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#notationDecl(java.lang.String, java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public void notationDecl(String name, String publicId, String systemId) throws SAXException { |
| delegate().notationDecl(name, publicId, systemId); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#prepare(org.eclipse.emf.ecore.xmi.XMLResource, |
| * org.eclipse.emf.ecore.xmi.XMLHelper, java.util.Map) |
| */ |
| @Override |
| public void prepare(XMLResource resource, XMLHelper xmlHelper, Map<?, ?> options) { |
| delegate().prepare(resource, xmlHelper, options); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#processingInstruction(java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public void processingInstruction(String target, String data) { |
| delegate().processingInstruction(target, data); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#reset() |
| */ |
| @Override |
| public void reset() { |
| delegate().reset(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#resolveEntity(java.lang.String, java.lang.String) |
| */ |
| @Override |
| public InputSource resolveEntity(String publicId, String systemId) throws SAXException { |
| return delegate().resolveEntity(publicId, systemId); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#setDocumentLocator(org.xml.sax.Locator) |
| */ |
| @Override |
| public void setDocumentLocator(Locator locator) { |
| delegate().setDocumentLocator(locator); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#skippedEntity(java.lang.String) |
| */ |
| @Override |
| public void skippedEntity(String name) throws SAXException { |
| delegate().skippedEntity(name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startCDATA() |
| */ |
| @Override |
| public void startCDATA() { |
| delegate().startCDATA(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startDocument() |
| */ |
| @Override |
| public void startDocument() { |
| delegate().startDocument(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startDTD(java.lang.String, java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public void startDTD(String name, String publicId, String systemId) { |
| delegate().startDTD(name, publicId, systemId); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startElement(java.lang.String, java.lang.String, |
| * java.lang.String) |
| */ |
| @Override |
| public void startElement(String uri, String localName, String name) { |
| delegate().startElement(uri, localName, name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startElement(java.lang.String, java.lang.String, |
| * java.lang.String, org.xml.sax.Attributes) |
| */ |
| @Override |
| public void startElement(String uri, String localName, String qName, Attributes attributes) |
| throws SAXException { |
| delegate().startElement(uri, localName, qName, attributes); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startEntity(java.lang.String) |
| */ |
| @Override |
| public void startEntity(String name) { |
| delegate().startEntity(name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#startPrefixMapping(java.lang.String, java.lang.String) |
| */ |
| @Override |
| public void startPrefixMapping(String prefix, String uri) { |
| delegate().startPrefixMapping(prefix, uri); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see java.lang.Object#toString() |
| */ |
| @Override |
| public String toString() { |
| return delegate().toString(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#unparsedEntityDecl(java.lang.String, java.lang.String, |
| * java.lang.String, java.lang.String) |
| */ |
| @Override |
| public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) |
| throws SAXException { |
| delegate().unparsedEntityDecl(name, publicId, systemId, notationName); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#warning(org.xml.sax.SAXParseException) |
| */ |
| @Override |
| public void warning(SAXParseException e) throws SAXException { |
| delegate().warning(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#warning(org.eclipse.emf.ecore.xmi.XMIException) |
| */ |
| @Override |
| public void warning(XMIException e) { |
| delegate().warning(e); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#getXSIType() |
| */ |
| @Override |
| protected String getXSIType() { |
| final String key = "getXSIType1"; //$NON-NLS-1$ |
| return (String)reflectiveCall(key, delegate(), "getXSIType"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#handleObjectAttribs(org.eclipse.emf.ecore.EObject) |
| */ |
| @Override |
| protected void handleObjectAttribs(EObject obj) { |
| final String key = "handleObjectAttribs1"; //$NON-NLS-1$ |
| reflectiveCall(key, delegate(), "handleObjectAttribs", obj); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Calls a given method through reflection after setting it |
| * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean) accessible}. |
| * |
| * @param key |
| * The key of the bucket in which the Method object is stored within {@link #METHOD_CACHE}. |
| * @param target |
| * Target upon which we should invoke this method. |
| * @param methodName |
| * Name of the method we are to call. |
| * @param params |
| * Parameters of the invocation. |
| * @return Result of the invocation. |
| */ |
| protected static Object reflectiveCall(String key, Object target, String methodName, Object... params) { |
| Method method = METHOD_CACHE.get(key); |
| if (method == null) { |
| Class<?>[] paramTypes = new Class<?>[params.length]; |
| for (int i = 0; i < params.length; i++) { |
| paramTypes[i] = params[i].getClass(); |
| } |
| final Method temp = findMethod(methodName, paramTypes); |
| if (temp != null) { |
| // We'll make it accessible right now |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| public Object run() { |
| temp.setAccessible(true); |
| return null; |
| } |
| }); |
| METHOD_CACHE.put(key, temp); |
| } |
| method = temp; |
| } |
| |
| try { |
| if (method != null) { |
| return method.invoke(target, params); |
| } else { |
| throw new RuntimeException(new NoSuchMethodException("Could not find method " + methodName //$NON-NLS-1$ |
| + " on " + XMLHandler.class.getName())); //$NON-NLS-1$ |
| } |
| } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Sets the given field through reflection after setting it |
| * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean) accessible}. |
| * |
| * @param fieldName |
| * Name of the field we should set. |
| * @param target |
| * The actual object which field we are to set. |
| * @param value |
| * Value to which this field should be set. |
| */ |
| protected static void setField(String fieldName, Object target, Object value) { |
| try { |
| final Field field = FIELD_CACHE.get(fieldName); |
| if (field != null) { |
| field.set(target, value); |
| } else { |
| throw new RuntimeException(new NoSuchFieldException("Could not find field " + fieldName //$NON-NLS-1$ |
| + " on " + XMLHandler.class.getName())); //$NON-NLS-1$ |
| } |
| } catch (IllegalArgumentException | IllegalAccessException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Finds a field of the given name on {@link XMLHandler}. |
| * |
| * @param fieldName |
| * Name of the sought field. |
| * @return The field we were seeking if we could locate it. |
| */ |
| private static Field findField(String fieldName) { |
| final Field[] fields = XMLHandler.class.getDeclaredFields(); |
| Field result = null; |
| for (int i = 0; i < fields.length && result == null; i++) { |
| final Field candidate = fields[i]; |
| if (fieldName.equals(candidate.getName())) { |
| result = candidate; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Finds a method of the given name on {@link XMLHandler}. |
| * |
| * @param methodName |
| * Name of the sought method. |
| * @param paramTypes |
| * Types of the parameters of the method we seek. |
| * @return The method we were seeking if we could locate it. |
| */ |
| private static Method findMethod(String methodName, Class<?>... paramTypes) { |
| final Method[] methods = XMLHandler.class.getDeclaredMethods(); |
| Method result = null; |
| for (int i = 0; i < methods.length && result == null; i++) { |
| final Method candidate = methods[i]; |
| if (methodName.equals(candidate.getName()) |
| && equalArrays(paramTypes, candidate.getParameterTypes())) { |
| result = candidate; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Checks whether the two given arrays are equal. Contrarily to |
| * {@link java.util.Arrays#equals(Object[], Object[])}, this will consider an empty array to be equal to |
| * <code>null</code>. |
| * |
| * @param a1 |
| * First of the two arrays to compare. |
| * @param a2 |
| * Second of the two arrays to compare. |
| * @return <code>true</code> if the two given arrays are equal, <code>false</code> otherwise. |
| */ |
| private static boolean equalArrays(Object[] a1, Object[] a2) { |
| boolean equal = false; |
| if (a1 == null) { |
| equal = a2 == null || a2.length == 0; |
| } else if (a2 == null) { |
| equal = a1.length == 0; |
| } else if (a1.length != a2.length) { |
| equal = false; |
| } else { |
| equal = true; |
| for (int i = 0; i < a1.length && equal; i++) { |
| if (a1[i] != a2[i]) { |
| equal = false; |
| } |
| } |
| } |
| |
| return equal; |
| } |
| } |