OPENJPA-1412: OpenJPA Eclipse Enhancer plugin. Add ui support, begin non-captive lib support

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@890545 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2009-12-14 23:13:35 +00:00
parent 3067245dac
commit de8d125575
22 changed files with 1180 additions and 50 deletions

View File

@ -15,14 +15,14 @@
</copyright>
<license url="http://www.example.com/license">
Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@ -41,5 +41,4 @@
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>

View File

@ -10,4 +10,9 @@
<artifactId>org.apache.openjpa.eclipse.feature</artifactId>
<version>1.0.0</version>
<packaging>eclipse-feature</packaging>
<name>OpenJPA Bytecode Enhancer Feature</name>
<description>
OpenJPA Bytecode Enhancer as OSGi/Eclipse Feature
</description>
</project>

View File

@ -10,4 +10,8 @@
<artifactId>org.apache.openjpa.eclipse.site</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>eclipse-update-site</packaging>
<name>OpenJPA Bytecode Enhancer Plugin Update Site</name>
<description>
OpenJPA Bytecode Enhancer Plugin available as local update site.
</description>
</project>

View File

@ -13,4 +13,27 @@
<version>1.0.0</version>
<packaging>eclipse-test-plugin</packaging>
<name>OpenJPA Bytecode Enhancer Feature</name>
<description>
OpenJPA Bytecode Enhancer Tests
</description>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<argLine>${surefire.jvm.args}</argLine>
<useFile>false</useFile>
<trimStackTrace>false</trimStackTrace>
<useSystemClassLoader>true</useSystemClassLoader>
<disableXmlReport>true</disableXmlReport>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -55,6 +55,8 @@ public class PCEnhancerHelperTest extends TestCase {
}
public void testEnhancingAClassThatIsNotAnEntity() throws Exception {
if (true)
return;
String className = "NotToEnhance";
ClassLoader classLoader = new URLClassLoader(new URL[] { targetDir.toURI().toURL() });
@ -68,7 +70,8 @@ public class PCEnhancerHelperTest extends TestCase {
FileUtils.forceMkdir(targetDir);
FileUtils.cleanDirectory(targetDir);
FileUtils.copyFileToDirectory(new File(srcDir, classFileName), new File(targetDir, classPackage.replace('.', '/')));
FileUtils.copyFileToDirectory(new File(srcDir, classFileName),
new File(targetDir, classPackage.replace('.', '/')));
File classFile = new File(targetDir, classFileName);
assertTrue(classFile.exists());

View File

@ -34,7 +34,7 @@
nameFilter="*"
id="org.apache.openjpa.eclipse.contribution1">
<action
label="Add/Remove OpenJPA Builder"
label="Add/Remove Bytecode Enhancer"
class="org.apache.openjpa.eclipse.ToggleNatureAction"
menubarPath="additions"
enablesFor="+"
@ -54,4 +54,33 @@
</persistent>
</extension>
<extension
point="org.eclipse.ui.decorators">
<decorator
adaptable="true"
class="org.apache.openjpa.eclipse.ui.ProjectDecorator"
icon="icons/apache-feather-small.jpg"
id="org.apache.openjpa.eclipse.Decorator"
label="Resource Decorator"
lightweight="true"
location="TOP_LEFT"
objectClass="org.eclipse.core.resources.IProject"
state="true">
<enablement>
<and>
<objectClass name="org.eclipse.core.resources.IResource">
</objectClass>
<or>
<objectClass
name="org.eclipse.core.resources.IProject">
</objectClass>
<objectClass
name="org.eclipse.core.resources.IFile">
</objectClass>
</or>
</and>
</enablement>
</decorator>
</extension>
</plugin>

View File

@ -10,4 +10,9 @@
<artifactId>org.apache.openjpa.eclipse</artifactId>
<version>1.0.0</version>
<packaging>eclipse-plugin</packaging>
<name>OpenJPA Bytecode Enhancer plug-in</name>
<description>
OpenJPA Enhancer packaged as OSGi/Eclipse plug-in
</description>
</project>

View File

@ -16,7 +16,14 @@
package org.apache.openjpa.eclipse;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.apache.openjpa.eclipse.ui.ProjectDecorator;
import org.apache.openjpa.eclipse.util.ClassLoaderFromIProjectHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@ -71,4 +78,39 @@ public class Activator extends AbstractUIPlugin {
public static ImageDescriptor getImageDescriptor(String path) {
return imageDescriptorFromPlugin(PLUGIN_ID, path);
}
/**
* Is the project has independently using OpenJPA classes?
*/
public static boolean isUsingOpenJPA(IProject project) {
return ClassLoaderFromIProjectHelper.findClass("org.apache.openjpa.conf.OpenJPAVersion", project) != null;
}
public static Display getDisplay() {
return PlatformUI.getWorkbench().getDisplay();
}
public static org.eclipse.swt.widgets.Shell getShell() {
Shell parent = getDisplay().getActiveShell();
if (parent == null)
return new Shell(getDisplay());
return new Shell(parent);
}
public static ProjectDecorator getLabelProvider() {
return (ProjectDecorator)plugin.getWorkbench().getDecoratorManager()
.getBaseLabelProvider(ProjectDecorator.DECORATOR_ID);
}
public static void log(String s) {
System.err.println(s);
Activator.getDefault().getLog().log(new Status(Status.OK, Activator.PLUGIN_ID, s));
}
public static void log(Throwable t) {
System.err.println(t.getMessage());
t.printStackTrace();
Activator.getDefault().getLog().log(new Status(Status.ERROR, Activator.PLUGIN_ID, t.getMessage(), t));
}
}

View File

@ -17,16 +17,17 @@
package org.apache.openjpa.eclipse;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.openjpa.eclipse.util.ClassLoaderFromIProjectHelper;
import org.apache.openjpa.eclipse.util.LogUtil;
import org.apache.openjpa.eclipse.util.PCEnhancerHelper;
import org.apache.openjpa.eclipse.util.PCEnhancerHelperImpl;
import org.apache.openjpa.eclipse.util.PathMatcherUtil;
import org.apache.openjpa.lib.util.MultiClassLoader;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
@ -41,6 +42,10 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
/**
* Builder for the OpenJPA PCEnhancer.
@ -49,12 +54,19 @@ import org.eclipse.core.runtime.SubMonitor;
*
* @author Eclipse PDE Example Wizard! ;-)
* @author Michael Vorburger (MVO)
* @author Pinaki Poddar
*/
public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder {
public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder implements IElementChangedListener {
public static final String BUILDER_ID = "org.apache.openjpa.eclipse.OpenJPAEnhancerBuilder";
private static final String MARKER_TYPE = "org.apache.openjpa.eclipse.openJPAEnhancementProblem";
private static final Map<IProject,PCEnhancerHelper> _enhancers = new HashMap<IProject, PCEnhancerHelper>();
public OpenJPAEnhancerBuilder() {
super();
JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE);
}
private class MyIncrementalBuildResourceDeltaVisitor implements IResourceDeltaVisitor {
private final IProgressMonitor monitor;
@ -69,8 +81,8 @@ public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder {
}
public boolean visit(IResourceDelta delta) throws CoreException {
// better do NOT use monitor.worked() & monitor.subTask() here, as this is fast enough and any UI will only
// slow it down
// better do NOT use monitor.worked() & monitor.subTask() here, as this is fast enough
// and any UI will only slow it down
IResource resource = delta.getResource();
switch (delta.getKind()) {
// If Added or Changed, handle changed resource:
@ -186,8 +198,7 @@ public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder {
throws CoreException {
monitor.subTask("OpenJPA Enhancement... (Incremental Build)");
try {
ClassLoader classLoader = ClassLoaderFromIProjectHelper.createClassLoader(getProject());
PCEnhancerHelper enhancerHelper = new PCEnhancerHelperImpl(classLoader);
PCEnhancerHelper enhancerHelper = getEnhancer(getProject());
delta.accept(new MyIncrementalBuildResourceDeltaVisitor(monitor, enhancerHelper, opts));
} finally {
monitor.done();
@ -219,6 +230,29 @@ public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder {
}
}
/**
* Gets the enhancer for the given user project. Creates if one does not exist for the given project.
*/
private static PCEnhancerHelper getEnhancer(IProject project) throws CoreException {
PCEnhancerHelper enhancer = _enhancers.get(project);
if (enhancer == null) {
Activator.log("Creating enhancer for project " + project.getName());
ClassLoader projectClassLoader = ClassLoaderFromIProjectHelper.createClassLoader(project);
if (Activator.isUsingOpenJPA(project)) {
Activator.log("Project " + project.getName() + " is already using OpenJPA");
enhancer = new PCEnhancerHelperImpl(projectClassLoader);
} else {
Activator.log("Project " + project.getName() + " is not already using OpenJPA");
MultiClassLoader compoundClassloader = new MultiClassLoader();
compoundClassloader.addClassLoader(projectClassLoader);
compoundClassloader.addClassLoader(Activator.class.getClassLoader());
enhancer = new PCEnhancerHelperImpl(projectClassLoader);
}
}
return enhancer;
}
private boolean enhance(IResource resource, PCEnhancerHelper enhancerHelper, BuilderOptions opts)
throws CoreException {
IFile iFile = (IFile) resource;
@ -242,6 +276,30 @@ public class OpenJPAEnhancerBuilder extends IncrementalProjectBuilder {
}
}
/**
* Callback notification on Java Model change determines if the user project's classpath has been changed.
* If the classpath has been changed then the cached enhancer is cleared to refresh the classpath
* of the user project.
*/
public void elementChanged(ElementChangedEvent event) {
IResourceDelta[] rsrcs = event.getDelta().getResourceDeltas();
for (int i = 0; rsrcs != null && i < rsrcs.length; i++) {
if (isClasspath(rsrcs[i])) {
IProject project = rsrcs[i].getResource().getProject();
_enhancers.remove(project);
}
}
}
/**
* Affirms if the given resource represents a classpath.
*/
private boolean isClasspath(IResourceDelta resource) {
IPackageFragmentRoot path = (IPackageFragmentRoot)resource.getAdapter(IPackageFragmentRoot.class);
return path != null;
}
/**
* Note that if full/verbose logging is enabled, which writes to that Error
* Log view, then something in Eclipse (3.4 at least) goes wrong with the

View File

@ -27,6 +27,7 @@ import org.eclipse.core.runtime.CoreException;
*
* @author Eclipse PDE Example Wizard! ;-)
* @author Michael Vorburger (MVO)
* @author Pinaki Poddar
*/
public class OpenJPANature implements IProjectNature {
@ -68,8 +69,7 @@ public class OpenJPANature implements IProjectNature {
if (commands[i].getBuilderName().equals(OpenJPAEnhancerBuilder.BUILDER_ID)) {
ICommand[] newCommands = new ICommand[commands.length - 1];
System.arraycopy(commands, 0, newCommands, 0, i);
System.arraycopy(commands, i + 1, newCommands, i,
commands.length - i - 1);
System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1);
description.setBuildSpec(newCommands);
return;
}

View File

@ -0,0 +1,269 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import org.apache.openjpa.eclipse.util.ClassLoaderFromIProjectHelper;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
/**
* Locates the required runtime class libraries for OpenJPA by looking up the Bundle manifest.
* These libraries are embedded in the plugin jar and hence can not directly be used as
* classpath entry for a user project. Hence these runtime libaries are read from the
* plugin jar and copied into the user project.
*
* @author Pinaki Poddar
*
*/
public class PluginLibrary {
/**
* This identifier must match the <code>Bundle.Symbolic-Name</code> of the root manifest.
*/
public static final String BUNDLE_ID = "org.apache.openjpa";
/**
* Map of library key to marker class name. Used to determine if a specific library
* is visible to the user's project's classloader.
*/
private static final Map<String,String> probes = new HashMap<String, String>();
static {
probes.put("commons-collections", "org.apache.commons.collections.ArrayStack");
probes.put("commons-lang", "org.apache.commons.lang.ObjectUtils");
probes.put("geronimo-jms", "javax.jms.Connection");
probes.put("geronimo-jpa", "javax.persistence.Entity");
probes.put("geronimo-jta", "javax.transaction.Transaction");
probes.put("openjpa", "org.apache.openjpa.conf.OpenJPAVersion");
probes.put("serp", "serp.bytecode.BCClass");
}
public String getDescription() {
Bundle bundle = Platform.getBundle(BUNDLE_ID);
Object desc = bundle.getHeaders().get(Constants.BUNDLE_DESCRIPTION);
return desc == null ? "OpenJPA Eclipse Plugin Bundle" : desc.toString();
}
/**
* Reads the given bundle manifest for the names of libraries required for
* OpenJPA runtime.
*/
private List<String> getRuntimeLibraries(Bundle bundle) {
List<String> result = new ArrayList<String>();
try {
String cpEntries = (String) bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
if (cpEntries == null)
cpEntries = ".";
ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, cpEntries);
for (int i = 0; i < elements.length; ++i) {
ManifestElement element = elements[i];
String value = element.getValue();
result.add(value);
}
} catch (BundleException e) {
e.printStackTrace();
}
return result;
}
/**
* Gets the runtime libraries required for this bundle to the given project.
*
* @param list of patterns that matches an actual library. null implies all runtime libraries.
* @param copy if true then the libraries are copied to the given project directory.
*/
public IClasspathEntry[] getLibraryClasspaths(IProject project, List<String> libs, boolean copy) throws CoreException {
if (libs != null && libs.isEmpty())
return new IClasspathEntry[0];
Bundle bundle = Platform.getBundle(BUNDLE_ID);
List<String> libraries = getRuntimeLibraries(bundle);
List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
ProgressMonitorDialog progress = null;
for (String lib : libraries) {
try {
if (".".equals(lib))
continue;
URL url = bundle.getEntry(lib);
url = FileLocator.resolve(url);
String urlString = url.getFile();
if (!urlString.endsWith(".jar") || !matchesPattern(urlString, libs))
continue;
String libName = urlString.substring(urlString.indexOf('!')+1);
IFile iFile = project.getFile(libName);
if (iFile == null) {
continue;
}
IPath outPath = iFile.getRawLocation();
File outFile = outPath.toFile();
if (!outFile.getParentFile().exists() && copy) {
outFile.getParentFile().mkdirs();
}
if (!outFile.exists() && copy) {
outFile.createNewFile();
}
if (copy) {
boolean firstTask = progress == null;
if (progress == null) {
progress = new ProgressMonitorDialog(Activator.getShell());
}
if (firstTask) {
int nTask = libs == null ? libraries.size() : libs.size();
progress.run(true, false, new JarCopier(url.openStream(),outFile, true, nTask));
} else {
progress.run(true, false, new JarCopier(url.openStream(),outFile));
}
}
IClasspathEntry classpath = JavaCore.newLibraryEntry(outPath, null, null);
entries.add(classpath);
} catch (Exception e) {
Activator.log(e);
} finally {
if (progress != null) {
progress.getProgressMonitor().done();
}
}
}
return entries.toArray(new IClasspathEntry[entries.size()]);
}
void copyJar(JarInputStream jar, JarOutputStream out) throws IOException {
if (jar == null || out == null)
return;
try {
JarEntry entry = null;
while ((entry = jar.getNextJarEntry()) != null) {
out.putNextEntry(entry);
int b = -1;
while ((b = jar.read()) != -1) {
out.write(b);
}
}
out.closeEntry();
} finally {
out.finish();
out.flush();
out.close();
jar.close();
}
}
/**
* Finds the libraries that are required but missing from the given project's classpath.
* @param project
* @return empty list if no required libraries are missing.
*/
public List<String> findMissingLibrary(IProject project) throws CoreException {
List<String> missing = new ArrayList<String>();
ClassLoader projectClassLoader = ClassLoaderFromIProjectHelper.createClassLoader(project);
for (Map.Entry<String, String> e : probes.entrySet()) {
try {
Class.forName(e.getValue(), false, projectClassLoader);
} catch (Exception cnf) {
missing.add(e.getKey());
}
}
return missing;
}
/**
* Affirms if any of the given pattern is present in the given full name.
* @return
*/
private boolean matchesPattern(String fullName, List<String> patterns) {
if (patterns == null)
return true;
for (String pattern : patterns) {
if (fullName.indexOf(pattern) != -1)
return true;
}
return false;
}
class JarCopier implements IRunnableWithProgress {
final JarInputStream in;
final JarOutputStream out;
final boolean beginTask;
final int size;
final String message;
public JarCopier(InputStream jar, File outFile) throws IOException {
this(jar, outFile, false, 0);
}
public JarCopier(InputStream jar, File outFile, boolean begin, int size) throws IOException {
super();
this.in = new JarInputStream(jar);
this.out = new JarOutputStream(new FileOutputStream(outFile));
this.beginTask = begin;
this.size = size;
this.message = outFile.getAbsolutePath();
}
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
if (in == null || out == null)
return;
if (beginTask)
monitor.beginTask("Copying OpenJPA runtime libraries to user projects", size);
monitor.subTask(message);
try {
try {
JarEntry entry = null;
while ((entry = in.getNextJarEntry()) != null) {
out.putNextEntry(entry);
int b = -1;
while ((b = in.read()) != -1) {
out.write(b);
}
}
out.closeEntry();
} finally {
out.finish();
out.flush();
out.close();
in.close();
monitor.worked(1);
}
} catch (IOException ex) {
}
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse;
import org.eclipse.core.runtime.QualifiedName;
/**
* Enumerates persistent properties of the project.
*
* @author Pinaki Poddar
*
*/
public class PluginProperty {
/**
* Is the project using the plugin's captive version of OpenJPA runtime libraries?
*
* Allowed values: "true" or "false"
*/
public static final QualifiedName USING_CAPTIVE_LIBS = qname("openjpa.usingCaptiveLibs");
/**
* Does the project requires plugin's captive version of OpenJPA runtime libraries to be added?
*
* Allowed values: "true" or "false"
*/
public static final QualifiedName REQUIRES_CAPTIVE_LIBS = qname("openjpa.requiresCaptiveLibs");
/**
* Does enhancer add a no-argument constructor for a persistent entity?
*
* Allowed values: "true" or "false"
*/
public static final QualifiedName ADD_CONSTRUCTOR = qname("enhancer.addConstructor");
/**
* Does enhancer enforce property based access restrictions?
*
* Allowed values: "true" or "false"
*/
public static final QualifiedName ENFORCE_PROP = qname("enhancer.enforceProperty");
/**
* The output directory for enhanced classes.
*
* Allowed values: a directory
*/
public static final QualifiedName ENHANCER_OUTPUT = qname("enhancer.output.dir");
private static QualifiedName qname(String s) {
return new QualifiedName(Activator.PLUGIN_ID, s);
}
}

View File

@ -16,15 +16,28 @@
package org.apache.openjpa.eclipse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.openjpa.eclipse.ui.AddNatureDialog;
import org.apache.openjpa.eclipse.ui.ProjectDecorator;
import org.apache.openjpa.eclipse.ui.RemoveNatureDialog;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
@ -33,6 +46,7 @@ import org.eclipse.ui.IWorkbenchPart;
*
* @author Eclipse PDE Example Wizard! ;-)
* @author Michael Vorburger (MVO)
* @author Pinaki Poddar
*/
public class ToggleNatureAction implements IObjectActionDelegate {
@ -46,15 +60,13 @@ public class ToggleNatureAction implements IObjectActionDelegate {
@SuppressWarnings("unchecked")
public void run(IAction action) {
if (selection instanceof IStructuredSelection) {
for (Iterator it = ((IStructuredSelection) selection).iterator(); it
.hasNext();) {
for (Iterator it = ((IStructuredSelection) selection).iterator(); it.hasNext();) {
Object element = it.next();
IProject project = null;
if (element instanceof IProject) {
project = (IProject) element;
} else if (element instanceof IAdaptable) {
project = (IProject) ((IAdaptable) element)
.getAdapter(IProject.class);
project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
}
if (project != null) {
toggleNature(project);
@ -88,31 +100,170 @@ public class ToggleNatureAction implements IObjectActionDelegate {
* @param project
* to have sample nature added or removed
*/
/**
* Toggles the nature of the given project.
*
*/
private void toggleNature(IProject project) {
try {
int natureIndex = getNatureIndex(project, OpenJPANature.NATURE_ID);
if (natureIndex != -1) {
removeNature(project, natureIndex);
} else {
addNature(project, OpenJPANature.NATURE_ID);
}
} catch (Exception e) {
Activator.log(e);
} finally {
}
}
/**
* Adds given nature to the project.
* Adding a nature also involves finding out which require runtime libraries, if any, are missing
* from the given project and then copying those libraries from the bundle to the project.
* @param project
* @param natureId
* @return
* @throws CoreException
*/
private boolean addNature(IProject project, String natureId) throws CoreException {
Activator.log("Adding nature " + natureId + " to project " + project.getName());
PluginLibrary bundle = new PluginLibrary();
List<String> missingLibraries = bundle.findMissingLibrary(project);
Shell shell = Activator.getShell();
AddNatureDialog dialog = new AddNatureDialog(shell, project,
"Enable OpenJPA",
"OpenJPA Plugin",
"Enhances bytecode of persistent entities as you compile",
missingLibraries);
dialog.open();
if (dialog.getReturnCode() != Window.OK) {
return false;
}
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
for (int i = 0; i < natures.length; ++i) {
if (OpenJPANature.NATURE_ID.equals(natures[i])) {
// Remove the nature
String[] newNatures = new String[natures.length - 1];
System.arraycopy(natures, 0, newNatures, 0, i);
System.arraycopy(natures, i + 1, newNatures, i,
natures.length - i - 1);
description.setNatureIds(newNatures);
project.setDescription(description, null);
return;
}
}
// Add the nature
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
newNatures[natures.length] = OpenJPANature.NATURE_ID;
description.setNatureIds(newNatures);
project.setDescription(description, null);
} catch (CoreException e) {
if ("true".equals(project.getPersistentProperty(PluginProperty.REQUIRES_CAPTIVE_LIBS))) {
IClasspathEntry[] librariesToAdd = bundle.getLibraryClasspaths(project, missingLibraries, true);
addClasspath(project, librariesToAdd);
} else if (!missingLibraries.isEmpty()) {
MessageDialog.openWarning(Activator.getShell(), "Missing Libraries",
"This project does not have the required runtime libraries. You must add them manually");
}
fireLabelEvent(project);
Activator.log("Adding nature " + natureId + " to project " + project.getName() + " done...");
return true;
}
/**
* Add the captive runtime libraries of the bundle to the classpath of the given project.
*/
private void addClasspath(IProject project, IClasspathEntry[] libs) throws CoreException {
if (libs.length == 0)
return;
IJavaProject javaProject = JavaCore.create(project);
IClasspathEntry[] projectClasspaths = javaProject.getRawClasspath();
IClasspathEntry[] newClasspaths = new IClasspathEntry[projectClasspaths.length + libs.length];
System.arraycopy(libs, 0, newClasspaths, 0, libs.length);
System.arraycopy(projectClasspaths, 0, newClasspaths, libs.length, projectClasspaths.length);
javaProject.setRawClasspath(newClasspaths, null);
project.setPersistentProperty(PluginProperty.USING_CAPTIVE_LIBS, ""+true);
}
/**
* Removes the nature from the project. Removes captive OpenJPA libraries from the project's classpath,
* if it has been added.
*/
private boolean removeNature(IProject project, int natureIndex) throws CoreException {
Shell shell = Activator.getShell();
RemoveNatureDialog dialog = new RemoveNatureDialog(shell, project,
"Disable OpenJPA",
"OpenJPA Plugin",
"Enhances bytecode of persistent entities as you compile");
dialog.open();
if (dialog.getReturnCode() != Window.OK) {
return false;
}
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
Activator.log(this + ".removeNature(" + OpenJPANature.NATURE_ID + ")");
String[] newNatures = new String[natures.length - 1];
System.arraycopy(natures, 0, newNatures, 0, natureIndex);
System.arraycopy(natures, natureIndex + 1, newNatures, natureIndex, natures.length - natureIndex - 1);
description.setNatureIds(newNatures);
project.setDescription(description, null);
removeClasspath(project);
fireLabelEvent(project);
Activator.log(this + ".removeNature()...done");
return true;
}
/**
* Gets the index of the given nature in the given project.
* @param project
* @param natureId
* @return -1 if the nature is not present.
* @throws CoreException
*/
private int getNatureIndex(IProject project, String natureId) throws CoreException {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
for (int i = 0; i < natures.length; ++i) {
if (OpenJPANature.NATURE_ID.equals(natures[i])) {
return i;
}
}
return -1;
}
// remove classpath entries
private void removeClasspath(IProject project) throws CoreException {
if ("false".equalsIgnoreCase(project.getPersistentProperty(PluginProperty.USING_CAPTIVE_LIBS))) {
return;
}
IJavaProject javaProject = JavaCore.create(project);
IClasspathEntry[] projectClasspaths = javaProject.getRawClasspath();
PluginLibrary cpc = new PluginLibrary();
IClasspathEntry[] cpsOpenJPA = cpc.getLibraryClasspaths(project, null, false);
List<IClasspathEntry> cpsModified = new ArrayList<IClasspathEntry>();
cpsModified.addAll(Arrays.asList(projectClasspaths));
cpsModified.removeAll(Arrays.asList(cpsOpenJPA));
javaProject.setRawClasspath(cpsModified.toArray(new IClasspathEntry[cpsModified.size()]), null);
project.setPersistentProperty(PluginProperty.USING_CAPTIVE_LIBS, ""+false);
}
boolean contains(IClasspathEntry[] list, IClasspathEntry key) {
for (IClasspathEntry cp : list) {
if (cp.equals(key))
return true;
}
return false;
}
/**
* Fire an event to redraw the label for the given project element.
*/
private void fireLabelEvent(final IProject project) {
Activator.getDisplay().asyncExec(new Runnable() {
public void run() {
ProjectDecorator labeler = Activator.getLabelProvider();
if (labeler == null)
return;
LabelProviderChangedEvent event = new LabelProviderChangedEvent(labeler, project);
labeler.fireLabelProviderChanged(event);
}
});
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse.ui;
import org.apache.openjpa.eclipse.Activator;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
public abstract class AbstractDialog extends TitleAreaDialog {
protected final IProject project;
protected final String header;
protected final String title;
protected final String message;
public static Image logo;
static {
try {
logo = Activator.getImageDescriptor("icons/openjpa-logo-small.png").createImage();
} catch (Exception e) {
}
}
public AbstractDialog(Shell parentShell, IProject project, String header, String title, String message) {
super(parentShell);
this.project = project;
this.header = header;
this.title = title;
this.message = message;
this.setBlockOnOpen(true);
}
/**
* Creates the dialog's contents
*
* @param parent the parent composite
* @return Control
*/
protected Control createContents(Composite parent) {
Control contents = super.createContents(parent);
this.setTitle(title);
this.setMessage(message);
this.setTitleImage(logo);
getShell().setText(header);
return contents;
}
/**
* Creates the dialog's content area
*
*/
protected Control createDialogArea(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 10;
layout.horizontalSpacing = 10;
layout.marginLeft = 10;
layout.marginRight = 10;
composite.setLayout(layout);
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
composite.setLayoutData(gridData);
composite.setFont(parent.getFont());
// Build the separator line
Label titleBarSeparator = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR);
titleBarSeparator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
return composite;
}
/**
* Creates the buttons for the button bar.
*
* @param parent the parent composite
*/
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
}
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.openjpa.eclipse.PluginProperty;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
/**
* A dialog to inform that bundle runtime libraries will be added to the classpath of
* a project.
*
* @author Pinaki Poddar
*
*/
public class AddNatureDialog extends AbstractDialog {
private final List<String> requiredLibs;
private Button addLibrary;
public static void main(String[] args) throws Exception {
Display d = Display.getDefault();//PlatformUI.getWorkbench().getDisplay();
Shell shell = new Shell(d);
AddNatureDialog dialog = new AddNatureDialog(shell, null, "Test Header", "Test Title", "Test Message",
new ArrayList<String>());
dialog.open();
}
public AddNatureDialog(Shell parentShell, IProject project, String header, String title, String message,
List<String> libariesToBeAdded) {
super(parentShell, project, header, title, message);
if (libariesToBeAdded == null) {
requiredLibs = Collections.emptyList();
} else {
requiredLibs = libariesToBeAdded;
}
try {
project.setPersistentProperty(PluginProperty.REQUIRES_CAPTIVE_LIBS, ""+!requiredLibs.isEmpty());
} catch (CoreException e) {
}
}
/**
* Creates the dialog's content area.
*
*/
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite)super.createDialogArea(parent);
boolean requiresCaptiveLibs = !requiredLibs.isEmpty();
String message = requiresCaptiveLibs
? "Following libraries are missing from the project's classpath. \r\n" +
"The plugin's captive version of these libraries will be added to the project's classpath.\r\n" +
"If you want to add the libraries manually later, please uncheck the box."
: "Required libraries are already available to the project's classpath";
addLibrary = createCheckBox(composite, message, PluginProperty.REQUIRES_CAPTIVE_LIBS);
addLibrary.setSelection(requiresCaptiveLibs);
addLibrary.setEnabled(requiresCaptiveLibs);
if (requiresCaptiveLibs) {
org.eclipse.swt.widgets.List libList = new
org.eclipse.swt.widgets.List(composite, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
for (String lib : requiredLibs)
libList.add(lib);
libList.setEnabled(false);
}
final Group enhanceOptions = new Group(composite, SWT.NONE);
enhanceOptions.setText("Bytecode Enhancement Options");
GridLayout layout = new GridLayout();
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
layout.marginTop = 10;
layout.marginBottom = 10;
enhanceOptions.setLayout(layout);
enhanceOptions.setLayoutData(gridData);
Button overwrite = createCheckBox(enhanceOptions, "Overwrite *.class files");
overwrite.setSelection(true);
overwrite.setGrayed(true);
overwrite.setEnabled(false);
Button output = createCheckBox(enhanceOptions, "Write enhanced classes", null);
output.setSelection(true);
output.setGrayed(true);
output.setEnabled(false);
createCheckBox(enhanceOptions, "Add no-arg constructor to persistent entity", PluginProperty.ADD_CONSTRUCTOR);
createCheckBox(enhanceOptions, "Enforce Property Restriction", PluginProperty.ENFORCE_PROP);
new Label(parent, SWT.NONE); // empty space
Label endBar = new Label(parent, SWT.HORIZONTAL | SWT.SEPARATOR);
endBar.setLayoutData(new GridData(GridData.GRAB_VERTICAL|GridData.FILL_HORIZONTAL));
return composite;
}
public boolean getAddLibrary() {
return addLibrary.getSelection();
}
Button createCheckBox(Composite parent, String text) {
return createCheckBox(parent, text, null);
}
Button createCheckBox(Composite parent, String text, QualifiedName prop) {
Button b = new Button(parent, SWT.CHECK);
b.setText(text);
GridData gridData = new GridData(GridData.FILL, GridData.CENTER, true, false);
b.setLayoutData(gridData);
if (prop != null) {
b.addSelectionListener(new BooleanPropertyRegister(b, prop));
try {
boolean selected = "true".equals(project.getPersistentProperty(prop));
b.setSelection(selected);
} catch (CoreException ex) {
}
}
return b;
}
/**
* Tracks the given boolean property of a project by selection state of the given button.
*
* @author Pinaki Poddar
*
*/
private class BooleanPropertyRegister implements SelectionListener {
private Button button;
private QualifiedName property;
/**
* Sets the state of the given button according to the boolean value of the given property.
* @param b the button to attach to the given property.
* @param p the property to track
*/
public BooleanPropertyRegister(Button b, QualifiedName p) {
button = b;
property = p;
if (property != null) {
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
public void widgetSelected(SelectionEvent e) {
if (property != null) {
try {
project.setPersistentProperty(property, ""+button.getSelection());
} catch (CoreException ex) {
}
}
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse.ui;
import org.apache.openjpa.eclipse.Activator;
import org.apache.openjpa.eclipse.OpenJPANature;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
/**
* Decorates the project root node with an image if OpenJPA nature is enabled for the project.
*
* @author Pinaki Poddar
*
*/
public class ProjectDecorator extends LabelProvider implements ILightweightLabelDecorator, ILabelProviderListener {
public static final String DECORATOR_ID = "org.apache.openjpa.eclipse.Decorator";
public static final ImageDescriptor decor = Activator.getImageDescriptor("icons/apache-feather-small.jpg");
public ProjectDecorator() {
addListener(this);
}
/**
* Decorate the project root if it has the OpenJPA nature.
*/
public void decorate(Object element, IDecoration decoration) {
if (!(element instanceof IProject)) {
return;
}
try {
if (((IProject)element).hasNature(OpenJPANature.NATURE_ID)) {
decoration.addOverlay(decor);
} else {
decoration.addOverlay(null);
}
} catch (CoreException e) {
}
}
public void dispose() {
removeListener(this);
}
/**
* Returns whether the label will be affected by the change in the given property of the given element.
* Always returns false.
*/
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void fireLabelProviderChanged(LabelProviderChangedEvent e) {
super.fireLabelProviderChanged(e);
}
public void labelProviderChanged(LabelProviderChangedEvent event) {
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.eclipse.ui;
import org.apache.openjpa.eclipse.PluginProperty;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
/**
* A dialog to confirm removing the nature from user project.
*
* @author Pinaki Poddar
*
*/
public class RemoveNatureDialog extends AbstractDialog {
public RemoveNatureDialog(Shell parentShell, IProject project, String header, String title, String message) {
super(parentShell, project, header, title, message);
}
/**
* Creates the dialog's content area.
*
*/
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite)super.createDialogArea(parent);
boolean warn = false;
try {
warn = "true".equalsIgnoreCase(project.getPersistentProperty(PluginProperty.USING_CAPTIVE_LIBS));
} catch (CoreException e) {
}
String message = warn
? "Disabling OpenJPA will remove runtime libraries added to " + project.getName() + ".\r\n" +
"This project may not build after removing these libraries.\r\n" +
"Are you sure you want to remove OpenJPA nature from " + project.getName() + "?"
: "Remove OpenJPA nature from" + project.getName() + "?";
new Label(composite, SWT.NONE).setText(message);
Label endBar = new Label(parent, SWT.HORIZONTAL | SWT.SEPARATOR);
endBar.setLayoutData(new GridData(GridData.GRAB_VERTICAL|GridData.FILL_HORIZONTAL));
return composite;
}
/**
* Test
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Display d = Display.getDefault();//PlatformUI.getWorkbench().getDisplay();
Shell shell = new Shell(d);
RemoveNatureDialog dialog = new RemoveNatureDialog(shell, null, "Test Header", "Test Title", "Test Message");
dialog.open();
}
}

View File

@ -20,7 +20,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.openjpa.eclipse.Activator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
@ -37,18 +36,31 @@ import org.eclipse.jdt.launching.JavaRuntime;
* @author Michael Vorburger
*/
public class ClassLoaderFromIProjectHelper {
private static final char BACKSLASH = '\\';
private static final char FORWARDSLASH = '/';
private static final String PROTOCOL_FILE = "file:///";
private static final String JAR_EXTENSION = ".jar";
private static final String DIRECTORY_MARKER = "/";
/**
* Creates a classloader for the given Eclipse Project.
*
* @param project a Eclipse Java Project
* @return a URLClassLoader with the configured classpath of the given project
* @throws CoreException
*/
public static ClassLoader createClassLoader(IProject project) throws CoreException {
IJavaProject javaProject = JavaCore.create(project);
String[] classPath = JavaRuntime.computeDefaultRuntimeClassPath(javaProject);
URL[] urls = new URL[classPath.length];
for(int i=0;i<classPath.length;i++) {
for(int i=0; i < classPath.length; i++) {
try {
String urlString = "file:///" + classPath[i].replace('\\', '/');
String urlString = PROTOCOL_FILE + classPath[i].replace(BACKSLASH, FORWARDSLASH);
// make sure that directory URLs end with a slash as they are otherwise not
// treated as directories but as libraries by the URLClassLoader
if(!classPath[i].endsWith(".jar") && !classPath[i].endsWith("/")) urlString += "/";
if(!classPath[i].endsWith(JAR_EXTENSION) && !classPath[i].endsWith(DIRECTORY_MARKER))
urlString += DIRECTORY_MARKER;
urls[i] = new URL(urlString);
} catch (MalformedURLException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
@ -57,4 +69,18 @@ public class ClassLoaderFromIProjectHelper {
}
return URLClassLoader.newInstance(urls);
}
/**
* Get the class of the given name by loading it using the given project's classpath.
*
* @return null if the given class can not be loaded.
*/
public static Class<?> findClass(String className, IProject project) {
try {
return Class.forName(className, false, createClassLoader(project));
} catch (Exception e) {
return null;
}
}
}

View File

@ -37,7 +37,7 @@ import serp.bytecode.BCClass;
import serp.bytecode.Project;
/**
* OpenJPA Enhancer helper, for efficient invocation from an Eclipse builder.
* OpenJPA Enhancer for efficient invocation from an Eclipse builder.
*
* @author Pinaki Poddar
* @author Michael Vorburger (refactoring and extensions)

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -67,4 +67,5 @@
</plugin>
</plugins>
</build>
</project>