blob: 43db7de935ea453f3f00b404fc886c67a691b430 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.internal;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jpt.core.internal.IJpaProject.Config;
import org.eclipse.jpt.utility.internal.ClassTools;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.model.AbstractModel;
/**
* The JPA model is synchronized so all changes to the list of JPA projects
* are thread-safe.
*
* The JPA model holds on to a list of JPA project configs and only instantiates
* their associated JPA projects when necessary. Other than performance,
* this should be transparent to clients.
*/
public class JpaModel extends AbstractModel implements IJpaModel {
/** maintain a list of all the current JPA projects */
private ArrayList<IJpaProjectHolder> jpaProjectHolders = new ArrayList<IJpaProjectHolder>();
// ********** constructor **********
/**
* Construct a JPA model and populate it with JPA projects to be built
* from the specified set of JPA project configs.
* The JPA model can only be instantiated by the JPA model manager.
*/
JpaModel(Iterable<IJpaProject.Config> configs) {
super();
for (IJpaProject.Config config : configs) {
this.addJpaProject(config);
}
}
// ********** IJpaModel implementation **********
/**
* This will trigger the instantiation of the JPA project associated with the
* specified Eclipse project.
*/
public synchronized IJpaProject jpaProject(IProject project) throws CoreException {
return this.jpaProjectHolder(project).jpaProject();
}
/**
* We can answer this question without instantiating the
* associated JPA project.
*/
public synchronized boolean containsJpaProject(IProject project) {
return this.jpaProjectHolder(project).holdsJpaProjectFor(project);
}
/**
* This will trigger the instantiation of all the JPA projects.
*/
public synchronized Iterator<IJpaProject> jpaProjects() throws CoreException {
// force the CoreException to occur here (instead of later, in Iterator#next())
ArrayList<IJpaProject> jpaProjects = new ArrayList<IJpaProject>(this.jpaProjectHolders.size());
for (IJpaProjectHolder holder : this.jpaProjectHolders) {
jpaProjects.add(holder.jpaProject());
}
return jpaProjects.iterator();
}
/**
* We can answer this question without instantiating any JPA projects.
*/
public synchronized int jpaProjectsSize() {
return this.jpaProjectHolders.size();
}
public synchronized IJpaFile jpaFile(IFile file) throws CoreException {
IJpaProject jpaProject = this.jpaProject(file.getProject());
return (jpaProject == null) ? null : jpaProject.jpaFile(file);
}
// ********** internal methods **********
/**
* never return null
*/
private IJpaProjectHolder jpaProjectHolder(IProject project) {
for (IJpaProjectHolder holder : this.jpaProjectHolders) {
if (holder.holdsJpaProjectFor(project)) {
return holder;
}
}
return NullJpaProjectHolder.instance();
}
/**
* Add a JPA project to the JPA model for the specified Eclipse project.
* JPA projects can only be added by the JPA model manager.
* The JPA project will only be instantiated later, on demand.
*/
synchronized void addJpaProject(IJpaProject.Config config) {
dumpStackTrace(); // figure out exactly when JPA projects are built
this.jpaProjectHolders.add(this.jpaProjectHolder(config.project()).buildJpaProjectHolder(this, config));
}
/**
* Remove the JPA project corresponding to the specified Eclipse project
* from the JPA model. Return whether the removal actually happened.
* JPA projects can only be removed by the JPA model manager.
*/
synchronized boolean removeJpaProject(IProject project) {
dumpStackTrace(); // figure out exactly when JPA projects are removed
if (containsJpaProject(project)) {
return this.jpaProjectHolder(project).remove();
}
return false;
}
/**
* Dispose the JPA model by disposing and removing all its JPA projects.
* The JPA model can only be disposed by the JPA model manager.
*/
synchronized void dispose() {
// clone the list to prevent concurrent modification exceptions
@SuppressWarnings("unchecked")
ArrayList<IJpaProjectHolder> holders = (ArrayList<IJpaProjectHolder>) this.jpaProjectHolders.clone();
for (IJpaProjectHolder holder : holders) {
holder.remove();
}
}
@Override
public void toString(StringBuilder sb) {
sb.append("JPA projects size: " + this.jpaProjectsSize());
}
// ********** events **********
synchronized void synchronizeFiles(IProject project, IResourceDelta delta) throws CoreException {
if (containsJpaProject(project)) {
this.synchronizeJpaFiles(project, delta);
}
}
/**
* Forward the specified resource delta to the JPA project corresponding
* to the specified Eclipse project.
*/
private void synchronizeJpaFiles(IProject project, IResourceDelta delta) throws CoreException {
this.jpaProjectHolder(project).synchronizeJpaFiles(delta);
}
/**
* Forward the Java element changed event to all the JPA projects
* because the event could affect multiple projects.
*/
synchronized void javaElementChanged(ElementChangedEvent event) {
for (IJpaProjectHolder jpaProjectHolder : this.jpaProjectHolders) {
jpaProjectHolder.javaElementChanged(event);
}
}
// ********** holder callbacks **********
/**
* called by the JPA project holder when the JPA project is actually
* instantiated
*/
/* private */ void jpaProjectBuilt(IJpaProject jpaProject) {
this.fireItemAdded(JPA_PROJECTS_COLLECTION, jpaProject);
}
/**
* called by the JPA project holder if the JPA project has been
* instantiated and we need to remove it
*/
/* private */ void jpaProjectRemoved(IJpaProject jpaProject) {
this.fireItemRemoved(JPA_PROJECTS_COLLECTION, jpaProject);
}
/**
* called by the JPA project holder
*/
/* private */ void removeJpaProjectHolder(IJpaProjectHolder jpaProjectHolder) {
this.jpaProjectHolders.remove(jpaProjectHolder);
}
// ********** JPA project holder **********
private interface IJpaProjectHolder {
boolean holdsJpaProjectFor(IProject project);
IJpaProject jpaProject() throws CoreException;
void synchronizeJpaFiles(IResourceDelta delta) throws CoreException;
void javaElementChanged(ElementChangedEvent event);
IJpaProjectHolder buildJpaProjectHolder(JpaModel jpaModel, IJpaProject.Config config);
boolean remove();
}
private static class NullJpaProjectHolder implements IJpaProjectHolder {
private static final IJpaProjectHolder INSTANCE = new NullJpaProjectHolder();
static IJpaProjectHolder instance() {
return INSTANCE;
}
// ensure single instance
private NullJpaProjectHolder() {
super();
}
public boolean holdsJpaProjectFor(IProject project) {
return false;
}
public IJpaProject jpaProject() throws CoreException {
return null;
}
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
// do nothing
}
public void javaElementChanged(ElementChangedEvent event) {
// do nothing
}
public IJpaProjectHolder buildJpaProjectHolder(JpaModel jpaModel, Config config) {
return new JpaProjectHolder(jpaModel, config);
}
public boolean remove() {
return false;
}
@Override
public String toString() {
return ClassTools.shortClassNameForObject(this);
}
}
/**
* Pair a JPA project config with its lazily-initialized JPA project.
*/
private static class JpaProjectHolder implements IJpaProjectHolder {
private final JpaModel jpaModel;
private final IJpaProject.Config config;
private IJpaProject jpaProject;
JpaProjectHolder(JpaModel jpaModel, IJpaProject.Config config) {
super();
this.jpaModel = jpaModel;
this.config = config;
}
public boolean holdsJpaProjectFor(IProject project) {
return this.config.project().equals(project);
}
public IJpaProject jpaProject() throws CoreException {
if (this.jpaProject == null) {
this.jpaProject = this.buildJpaProject();
// notify listeners of the JPA model
this.jpaModel.jpaProjectBuilt(this.jpaProject);
}
return this.jpaProject;
}
private IJpaProject buildJpaProject() throws CoreException {
return this.config.jpaPlatform().getJpaFactory().createJpaProject(this.config);
}
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
if (this.jpaProject != null) {
this.jpaProject.synchronizeJpaFiles(delta);
}
}
public void javaElementChanged(ElementChangedEvent event) {
if (this.jpaProject != null) {
this.jpaProject.javaElementChanged(event);
}
}
public IJpaProjectHolder buildJpaProjectHolder(JpaModel jm, Config c) {
throw new IllegalArgumentException(c.project().getName());
}
public boolean remove() {
this.jpaModel.removeJpaProjectHolder(this);
if (this.jpaProject != null) {
this.jpaModel.jpaProjectRemoved(this.jpaProject);
this.jpaProject.dispose();
}
return true;
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.config.project().getName());
}
}
// ********** debug **********
private static final boolean DEBUG = false;
private static void dumpStackTrace() {
if (DEBUG) {
// lock System.out so the stack elements are printed out contiguously
synchronized (System.out) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// skip the first 3 elements - those are this method and 2 methods in Thread
for (int i = 3; i < stackTrace.length; i++) {
StackTraceElement element = stackTrace[i];
if (element.getMethodName().equals("invoke0")) {
break; // skip all elements outside of the JUnit test
}
System.out.println("\t" + element);
}
}
}
}
}