blob: a6088a77362f768b8f1729f45a932fc160f4d376 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}