All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.extcos.internal.JavaResourceAccessor Maven / Gradle / Ivy

package net.sf.extcos.internal;

import static net.sf.extcos.util.Assert.iae;
import static net.sf.extcos.util.StringUtils.append;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.extcos.spi.AnnotationMetadata;
import net.sf.extcos.spi.QueryContext;
import net.sf.extcos.spi.ResourceAccessor;
import net.sf.extcos.util.Assert;
import net.sf.extcos.util.ClassUtils;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JavaResourceAccessor implements ResourceAccessor {
	private class BooleanHolder {
		boolean value;
	}

	private class NameHolder {
		String name;
	}

	private abstract class AnnotatedClassVisitor extends ClassVisitor {
		private AnnotatedClassVisitor() {
			super(Opcodes.ASM4);
		}

		@Override
		public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
			if (shouldVisitAnnotation(visible)) {
				if (annotations == null) {
					annotations = new HashMap();
				}
				return new AnnotationVisitorImpl(desc,
						shouldAcceptInheritedAnnotationsOnly());
			}

			return null;
		}

		protected abstract boolean shouldVisitAnnotation(boolean visible);

		protected abstract boolean shouldAcceptInheritedAnnotationsOnly();
	}

	private class GeneralVisitor extends AnnotatedClassVisitor {
		private final NameHolder nameHolder;
		private final BooleanHolder isClassHolder;

		private GeneralVisitor(final NameHolder nameHolder,
				final BooleanHolder isClassHolder) {
			this.nameHolder = nameHolder;
			this.isClassHolder = isClassHolder;
		}

		@Override
		public void visit(final int version, final int access, final String name,
				final String signature, final String superName,
				final String[] interfaces) {
			if (!(Modifier.isAbstract(access) ||
					Modifier.isInterface(access) ||
					excludeBecauseIsEnum(version, superName))) {
				isClassHolder.value = true;
				nameHolder.name = name;

				readInterfaces(superName, interfaces);
				readSuperClasses(superName);
			}
		}

		private boolean excludeBecauseIsEnum(final int version, final String superName) {
			boolean isEnum = false;
			
			if (version >= Opcodes.V1_5 && "java/lang/Enum".equals(superName)) {
				isEnum = true;
			}
			
			return isEnum && !QueryContext.getInstance().isIncludeEnums();
		}

		@Override
		public void visitInnerClass(final String name, final String outerName,
				final String innerName, final int access) {
			if (isClassHolder.value && nameHolder.name != null &&
					nameHolder.name.equals(name)) {
				isClassHolder.value = false;
			}
		}

		@Override
		public void visitOuterClass(final String owner, final String name, final String desc) {
			isClassHolder.value = false;
		}

		@Override
		protected boolean shouldVisitAnnotation(final boolean visible) {
			return isClassHolder.value && visible;
		}

		@Override
		protected boolean shouldAcceptInheritedAnnotationsOnly() {
			return false;
		}
	}

	private class AnnotationVisitorImpl extends AnnotationVisitor {
		private final AnnotationMetadataImpl metadata;
		private final String className;
		private final boolean inheritedOnly;

		private AnnotationVisitorImpl(final String desc, final boolean inheritedOnly) {
			super(Opcodes.ASM4);
			metadata = new AnnotationMetadataImpl();
			className = Type.getType(desc).getClassName();
			this.inheritedOnly = inheritedOnly;
		}

		@Override
		public void visit(final String name, final Object value) {
			metadata.putParameter(name, value);
		}

		@Override
		public void visitEnum(final String name, final String desc, final String value) {
			try {
				String enumName = Type.getType(desc).getClassName();
				Class enumClass = QueryContext.getInstance().getClassLoader().loadClass(enumName);
				Method valueOf = enumClass.getDeclaredMethod("valueOf",	String.class);
				Object object = valueOf.invoke(null, value);
				metadata.putParameter(name, object);
			} catch (Exception ex) {
				logger.warn("An exception occurred", ex);
			}
		}

		@Override
		public void visitEnd() {
			try {
				Class annotationClass =
						QueryContext.getInstance().getClassLoader().loadClass(className);
				
				if (inheritedOnly && annotationClass.isAnnotationPresent(Inherited.class) ||
						!inheritedOnly) {
					
					// Check declared default values of attributes in the annotation type.
					Method[] annotationAttributes = annotationClass.getMethods();
					for (Method annotationAttribute : annotationAttributes) {
						String attributeName = annotationAttribute.getName();
						Object defaultValue = annotationAttribute.getDefaultValue();
						if (defaultValue != null && !metadata.hasKey(attributeName)) {
							metadata.putParameter(attributeName, defaultValue);
						}
					}
					annotations.put(className, metadata);
				}
			}
			catch (ClassNotFoundException ex) {
				logger.error("Class not found - can't determine meta-annotations", ex);
			}
		}
	}

	private class AnnotationMetadataImpl implements AnnotationMetadata {
		private final Map parameters =
				new HashMap();

		@Override
		public Object getValue(final String key) {
			return parameters.get(key);
		}

		@Override
		public boolean hasKey(final String key) {
			return parameters.containsKey(key);
		}

		protected void putParameter(final String key, final Object value) {
			parameters.put(key, value);
		}
	}

	private static Logger logger = LoggerFactory.getLogger(JavaResourceAccessor.class);

	private static Method defineClass;
	private static Method resolveClass;

	static {
		try {
			AccessController.doPrivileged(
					new PrivilegedExceptionAction(){
						@Override
						public Void run() throws Exception{
							Class cl = Class.forName("java.lang.ClassLoader");

							defineClass = cl.getDeclaredMethod("defineClass",
									new Class[] { String.class, byte[].class,
									int.class, int.class });

							resolveClass = cl.getDeclaredMethod("resolveClass",
									Class.class);

							return null;
						}
					});
		}
		catch (PrivilegedActionException pae) {
			throw new RuntimeException("cannot initialize Java Resource Accessor", pae.getException());
		}
	}

	private static final int ASM_FLAGS =
			ClassReader.SKIP_DEBUG +
			ClassReader.SKIP_CODE  +
			ClassReader.SKIP_FRAMES;

	private byte[] resourceBytes;
	private URL resourceUrl;
	private String className;

	private Map annotations;
	private Set interfaces;
	private Set superClasses;
	private boolean isClass;

	@Override
	public Class generateClass() {
		if (!isClass) {
			return null;
		}

		Class clazz = null;
		ClassLoader loader = QueryContext.getInstance().getClassLoader();

		try {
			defineClass.setAccessible(true);
			resolveClass.setAccessible(true);

			clazz = (Class)defineClass.invoke(loader,
					className, resourceBytes, 0, resourceBytes.length);
			resolveClass.invoke(loader, clazz);
		} catch (InvocationTargetException e) {
			if (e.getCause() instanceof LinkageError) {
				try {
					clazz = Class.forName(className, true, loader);
				} catch (ClassNotFoundException e1) {
					logger.error(append("Error creating class from URL [",
							resourceUrl.toString(), "]"), e1);
				}
			} else {
				logger.error(append("Error creating class from URL [",
						resourceUrl.toString(), "]"), e.getCause());
			}
		} catch (Exception e) {
			logger.error(append("Error creating class from URL [",
					resourceUrl.toString(), "]"), e);
		} finally {
			defineClass.setAccessible(false);
			resolveClass.setAccessible(false);
		}

		return clazz;
	}

	@Override
	public AnnotationMetadata getAnnotationMetadata(
			final Class annotation) {
		if (isClass && annotations != null &&
				annotations.containsKey(annotation.getCanonicalName())) {
			return annotations.get(annotation.getCanonicalName());
		}

		return null;
	}

	@Override
	public boolean hasInterface(final Class interfaze) {
		if (isClass && interfaces != null) {
			return interfaces.contains(interfaze.getCanonicalName());
		}

		return false;
	}

	@Override
	public boolean isClass() {
		return isClass;
	}

	@Override
	public boolean isSubclassOf(final Class clazz) {
		if (clazz == Object.class) {
			return true;
		}

		if (isClass && superClasses != null) {
			return superClasses.contains(clazz.getCanonicalName());
		}

		return false;
	}

	@Override
	public void setResourceUrl(final URL resourceUrl) {
		Assert.notNull(resourceUrl, iae());

		try {
			this.resourceBytes = readBytes(resourceUrl);
			this.resourceUrl = resourceUrl;
			readClassData();
		} catch (IOException e) {
			isClass = false;
			logger.error("Error reading resource", e);
		}
	}

	private byte[] readBytes(final URL resourceUrl) throws IOException {
		InputStream classStream = new BufferedInputStream(resourceUrl.openStream());
		List buffer = new ArrayList();
		int readByte;

		while((readByte = classStream.read()) != -1) {
			buffer.add((byte)readByte);
		}

		byte[] bytes = new byte[buffer.size()];

		for (int i = 0; i < buffer.size(); i++) {
			bytes[i] = buffer.get(i);
		}

		return bytes;
	}

	private void readClassData() {
		BooleanHolder isClassHolder = new BooleanHolder();
		NameHolder nameHolder = new NameHolder();

		ClassReader reader = new ClassReader(resourceBytes);
		reader.accept(new GeneralVisitor(nameHolder, isClassHolder),
				ASM_FLAGS);

		isClass = isClassHolder.value;

		if (isClass) {
			className =
					ClassUtils.convertResourcePathToClassName(nameHolder.name);
		} else {
			// if the resource isn't a valid class, clean memory
			annotations   = null;
			interfaces    = null;
			superClasses  = null;
			resourceBytes = null;
			resourceUrl   = null;
		}
	}

	private void readSuperClasses(final String superName) {
		if (!"java/lang/Object".equals(superName)) {
			if (superClasses == null) {
				superClasses = new ArraySet();
			}

			String superClass = ClassUtils.convertResourcePathToClassName(
					superName);
			superClasses.add(superClass);

			try {
				ClassReader reader = new ClassReader(
						QueryContext.getInstance().getClassLoader().getResourceAsStream(superName + ".class"));

				reader.accept(new AnnotatedClassVisitor() {
					@Override
					public void visit(final int version, final int access, final String name,
							final String signature, final String superName, final String[] interfaces) {
						readSuperClasses(superName);
					}

					@Override
					protected boolean shouldVisitAnnotation(final boolean visible) {
						return visible;
					}

					@Override
					protected boolean shouldAcceptInheritedAnnotationsOnly() {
						return true;
					}
				}, ASM_FLAGS);
			} catch (Exception e) {
				if (logger.isErrorEnabled()) {
					logger.error("Unable to read super class [" + superClass + "]", e);
				}
			}
		}
	}

	private void readInterfaces(final String superName, final String[] interfaces) {
		if (this.interfaces == null && interfaces.length > 0) {
			this.interfaces = new ArraySet();
		}

		for (String interfaze : interfaces) {
			this.interfaces.add(
					ClassUtils.convertResourcePathToClassName(interfaze));
			readSuperInterfaces(interfaze);
		}

		readInheritedInterfaces(superName);
	}

	private void readInheritedInterfaces(final String superName) {
		if ("java/lang/Object".equals(superName)) {
			return;
		}

		readSuperInterfaces(superName);
	}

	private void readSuperInterfaces(final String type) {
		String interfaze = ClassUtils.convertResourcePathToClassName(type);

		try {
			ClassReader reader = new ClassReader(
					QueryContext.getInstance().getClassLoader().getResourceAsStream(type + ".class"));

			reader.accept(new ClassVisitor(Opcodes.ASM4) {
				@Override
				public void visit(final int version, final int access, final String name,
						final String signature, final String superName, final String[] interfaces) {
					readInterfaces(superName, interfaces);
				}
			}, ASM_FLAGS);
		} catch (Exception e) {
			if (logger.isErrorEnabled()) {
				logger.error("Unable to read interface [" + interfaze + "]", e);
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy