blob: f73a708d97ad69d7b4d61deadfe18d86e086ba77 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.codegen.genmodel;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.codegen.ecore.generator.Generator;
import org.eclipse.emf.codegen.ecore.generator.GeneratorAdapterFactory;
import org.eclipse.emf.codegen.ecore.genmodel.generator.GenModelGeneratorAdapter;
import org.eclipse.emf.codegen.jet.JETEmitter;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.examples.codegen.dynamic.JavaFileUtil;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleWiring;
//
// Overridden to allow static templates to be invoked.
//
@Deprecated /* @deprecated temporary workaround for Bug 543870 */
public class OCLGenModelGeneratorAdapter extends GenModelGeneratorAdapter
{
/**
* TemplateClassLoader overrides URLClassLoader to provide the option to return null
* from loadTemplateClass if the explicitly requested class is not found. Other, referenced
* classes, requested via loadClass fall-back on the classLoader if the urls access fails.
*
* This class and OCLGenModelGeneratorAdapter can be deleted if Bug 543870 fix is available.
*/
protected static class TemplateClassLoader extends URLClassLoader
{
/**
* The ClassLoader to be used to load not-nested classes referenced from a template class.
*/
private final @NonNull ClassLoader classLoader;
/**
* The root (qualified, but not-nested) template class names handled by this TemplateClassLoader.
*/
private final @NonNull Set<@NonNull String> templateClassNames = new HashSet<>();
public TemplateClassLoader(@NonNull URL url, @NonNull ClassLoader classLoader) {
super(new URL[]{url}, null); // Suppress default fall-back to parent.
assert url != null;
assert classLoader != null;
this.classLoader = classLoader;
}
/**
* Re-implement to load any nested class of any of the templateClassNames direct from its class file.
* Other classes are loaded by the normal classLoader.
*/
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
int index = className.indexOf("$");
String rootClassName = index >= 0 ? className.substring(0, index) : className;
if (templateClassNames.contains(rootClassName)) {
return super.loadClass(className, resolve);
}
else {
return classLoader.loadClass(className);
}
}
/**
* Load and return the templateClassName using the templateClassPath configuring the template
* class loader to also load the templateClassName's nested classes. Returns null if no
* template class found. This allows a variety of explicit template class paths to be tried
* before a fall back to the normal class loader.
*/
public Class<?> loadTemplateClass(@NonNull String templateClassName) throws ClassNotFoundException {
assert templateClassName != null;
Class<?> templateClass = super.loadClass(templateClassName, true);
if (templateClass != null) {
templateClassNames.add(templateClassName);
}
return templateClass;
}
}
/**
* Cached sorted standalone classpath entries.
*/
private @NonNull String[] sortedClasspath = null;
/**
* Map from templateClassPath to the TemplatePathClassLoader that loads direct from the class file. These classes
* remain loaded for the lifetime of this AbstractGeneratorAdapter. i.e. Re-open the GenModel
* editor to respond to changed classes.
*/
private Map<@NonNull File, @NonNull TemplateClassLoader> templatePath2classLoader = null;
public OCLGenModelGeneratorAdapter(@NonNull GeneratorAdapterFactory generatorAdapterFactory) {
super(generatorAdapterFactory);
}
/**
* Return the java.class.path entry that resolves absoluteTemplateName references.
* This is only useful when running standalone. When using OSGI, the appropriate bundle-specific
* class loader should be used instead.
*/
protected @Nullable String getStandaloneTemplateClassPath(@NonNull String workspaceName) {
URI absoluteTemplateURI = EcorePlugin.resolvePlatformResourcePath(workspaceName);
if (absoluteTemplateURI == null) {
return null;
}
if (absoluteTemplateURI.isArchive()) {
absoluteTemplateURI = URI.createURI(absoluteTemplateURI.authority() + absoluteTemplateURI.path());
}
String absoluteTemplateName = absoluteTemplateURI.isFile() ? absoluteTemplateURI.toFileString() : absoluteTemplateURI.toString();
if (absoluteTemplateName == null) { // Never happens
return null;
}
if (sortedClasspath == null) {
sortedClasspath = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
Arrays.sort(sortedClasspath);
}
int matchIndex = Arrays.binarySearch(sortedClasspath, absoluteTemplateName);
if (matchIndex >= 0) { // Never happens - unless the templateDirectory is actually the bin folder
return sortedClasspath[matchIndex];
}
int insertionPoint = -(matchIndex + 1); // classpath[insertionPoint-1] < absoluteTemplateName < classpath[insertionPoint]
if (insertionPoint <= 0) {
return sortedClasspath[0];
}
if (insertionPoint >= sortedClasspath.length) {
return sortedClasspath[sortedClasspath.length-1];
}
String prevClassElement = sortedClasspath[insertionPoint-1];
String nextClassElement = sortedClasspath[insertionPoint];
int prevLength = prevClassElement.length();
int nextLength = nextClassElement.length();
int absoluteTemplateNameLength = absoluteTemplateName.length();
for (int i = 0; i < absoluteTemplateNameLength; i++) {
char c = absoluteTemplateName.charAt(i);
if (i >= prevLength) { // prevClassElement is prefix
return prevClassElement;
}
if (i >= nextLength) { // nextClassElement is prefix
return nextClassElement;
}
char prevC = prevClassElement.charAt(i);
if (prevC != c) {
return nextClassElement;
}
char nextC = nextClassElement.charAt(i);
if (nextC != c) {
return prevClassElement;
}
}
return prevClassElement; // Never happens, unless there are duplicates in which case nextClassElement is the same.
}
/**
* If {@link Generator.Options#dynamicTemplates dynamic templates} are not being used,
* attempts to set the emitter to use an existing, precompiled template class
* that has the given method name and argument types.
* @since 2.5
*/
@Override
protected void setStaticTemplateClass(JETEmitter jetEmitter, String className, String methodName, Class<?>[] arguments)
{
assert className != null;
if (!getGenerator().getOptions().dynamicTemplates)
{
Class<?> templateClass = null;
List<String> userTemplatePath = getUserTemplatePath(); // Omit the built-in emf.codegen.ecore to avoid caching; go direct to the fallback
if (!userTemplatePath.isEmpty()) {
templateClass = getStaticUserTemplateClass(userTemplatePath, className);
}
try {
if (templateClass == null) { // Fall-back load as an ordinary class
templateClass = getClass().getClassLoader().loadClass(className);
}
Method emitterMethod = templateClass.getDeclaredMethod(methodName, arguments);
jetEmitter.setMethod(emitterMethod);
}
catch (Exception exception)
{
// It's okay for there not be a precompiled template, so fail quietly.
}
}
}
public @Nullable Class<?> getStaticUserTemplateClass(@NonNull List<String> userTemplatePath, @NonNull String className) {
if (templatePath2classLoader == null) {
templatePath2classLoader = new HashMap<>();
}
//
// Try to load the className direct from the classPath corresponding to each templatePath element,
//
for (String templateElement : userTemplatePath) {
try {
ClassLoader classLoader = getClass().getClassLoader();
File templateClassPathFile = null; // e.g. E:\...
URI uri = URI.createURI(templateElement);
if (uri.isPlatform()) {
URI deresolveURI = uri.isPlatformResource() ? URI.createPlatformResourceURI("/", false) : URI.createPlatformPluginURI("/", false);
URI workspaceURI = uri.deresolve(deresolveURI);
String workspaceName = "/" + workspaceURI.toString();
IWorkspaceRoot root = EcorePlugin.getWorkspaceRoot();
if (root != null) {
String projectName = workspaceURI.segment(0);
Bundle bundle = Platform.getBundle(projectName);
BundleWiring bundleWiring = ClassUtil.nonNullState(bundle.adapt(BundleWiring.class));
classLoader = bundleWiring.getClassLoader();
templateClassPathFile = JavaFileUtil.getOSGIClassPath(bundle);
}
else {
String templateClassPath2 = getStandaloneTemplateClassPath(workspaceName);
if (templateClassPath2 != null) {
templateClassPathFile = new File(templateClassPath2);
}
}
}
else if (uri.isFile()) {
templateClassPathFile = new File(uri.toString());
}
else {
templateClassPathFile = new File(uri.toFileString());
}
if (templateClassPathFile != null) {
TemplateClassLoader templatePathClassLoader = templatePath2classLoader.get(templateClassPathFile);
if (templatePathClassLoader == null) {
assert classLoader != null;
String templateClassPathString = URI.createFileURI(templateClassPathFile.toString()).toString(); // e.g. file:/E:/...
URL templateClassPathURL = new URL(templateClassPathFile.isFile() ? templateClassPathString : templateClassPathString + "/"); // e.g. file:/E:/... ...jar or .../bin/
templatePathClassLoader = new TemplateClassLoader(templateClassPathURL, classLoader);
templatePath2classLoader.put(templateClassPathFile, templatePathClassLoader);
}
return templatePathClassLoader.loadTemplateClass(className);
}
}
catch (Exception e) {
getClass();
// If some template path attempt fails just move on to the next one.}
}
}
return null;
}
}