| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.netbeans.modules.j2ee.persistence.wizard; |
| |
| import java.io.IOException; |
| import java.util.*; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Future; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ExecutableType; |
| import javax.lang.model.type.TypeMirror; |
| import javax.swing.ComboBoxModel; |
| import javax.swing.DefaultComboBoxModel; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModel; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelException; |
| import org.netbeans.modules.j2ee.metadata.model.api.support.annotation.PersistentObject; |
| import org.netbeans.modules.j2ee.persistence.api.EntityClassScope; |
| import org.netbeans.modules.j2ee.persistence.api.metadata.orm.Entity; |
| import org.netbeans.modules.j2ee.persistence.api.metadata.orm.EntityMappingsMetadata; |
| import org.netbeans.modules.j2ee.persistence.util.MetadataModelReadHelper; |
| import org.netbeans.modules.j2ee.persistence.util.MetadataModelReadHelper.State; |
| import org.netbeans.modules.j2ee.persistence.wizard.jpacontroller.JpaControllerUtil; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.ChangeSupport; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * |
| * @author Pavel Buzek |
| */ |
| public class EntityClosure { |
| private static final Logger LOG = Logger.getLogger(EntityClosure.class.getName()); |
| |
| // XXX this class needs a complete rewrite: the computing of the available |
| // entity classes and of the referenced classes need to be moved away. |
| |
| private final ChangeSupport changeSupport = new ChangeSupport(this); |
| |
| private Set<Entity> availableEntityInstances = new HashSet<Entity>(); |
| private Set<String> availableEntities = new HashSet<String>(); |
| private Set<String> wantedEntities = new HashSet<String>(); |
| private Set<String> selectedEntities = new HashSet<String>(); |
| private Set<String> referencedEntities = new HashSet<String>(); |
| private HashMap<String,Entity> fqnEntityMap = new HashMap<String,Entity>(); |
| private HashMap<String,Boolean> fqnIdExistMap = new HashMap<String,Boolean>(); |
| private boolean modelReady; |
| private boolean ejbModuleInvolved = false; |
| private boolean closureEnabled = true; |
| private Project project; |
| |
| private final MetadataModelReadHelper<EntityMappingsMetadata, List<Entity>> readHelper; |
| |
| private final MetadataModel<EntityMappingsMetadata> model; |
| |
| public static EntityClosure create(EntityClassScope entityClassScope, Project project) { |
| EntityClosure ec = new EntityClosure(entityClassScope, project); |
| ec.initialize(); |
| return ec; |
| } |
| |
| public static ComboBoxModel getAsComboModel(EntityClosure ec) { |
| return new EntityClosureComboBoxModel(ec); |
| } |
| |
| private EntityClosure(EntityClassScope entityClassScope, Project project) { |
| this.model = entityClassScope.getEntityMappingsModel(true); |
| this.project = project; |
| readHelper = MetadataModelReadHelper.create(model, new MetadataModelAction<EntityMappingsMetadata, List<Entity>>() { |
| @Override |
| public List<Entity> run(EntityMappingsMetadata metadata) { |
| return Arrays.<Entity>asList(metadata.getRoot().getEntity()); |
| } |
| }); |
| } |
| |
| private void initialize() { |
| readHelper.addChangeListener(new ChangeListener() { |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| if (readHelper.getState() == State.FINISHED) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| addAvaliableEntities(new HashSet<Entity>(readHelper.getResult())); |
| modelReady = true; |
| changeSupport.fireChange(); |
| } catch (ExecutionException e) { |
| Exceptions.printStackTrace(e); |
| } |
| } |
| }); |
| } |
| } |
| }); |
| readHelper.start(); |
| } |
| |
| public void addAvaliableEntities(final Set<Entity> entities) { |
| availableEntityInstances.addAll(entities); |
| |
| JavaSource source = null; |
| try { |
| source = model.runReadAction(new MetadataModelAction<EntityMappingsMetadata, JavaSource>() { |
| |
| @Override |
| public JavaSource run(EntityMappingsMetadata metadata) throws Exception { |
| return metadata.createJavaSource(); |
| } |
| }); |
| } catch (MetadataModelException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if( source!=null ) { |
| try { |
| ClassPath classPath = source.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.COMPILE); |
| final Set<Project> ejbProjects = getEjbModulesOfClasspath(classPath); |
| source.runUserActionTask(new Task<CompilationController>() { |
| |
| @Override |
| public void run(CompilationController parameter) throws Exception { |
| for (Entity en : entities) { |
| availableEntities.add(en.getClass2()); |
| fqnEntityMap.put(en.getClass2(), en); |
| PersistentObject po = (PersistentObject) en; |
| ElementHandle<TypeElement> teh = po.getTypeElementHandle(); |
| TypeElement te = teh.resolve(parameter); |
| fqnIdExistMap.put(en.getClass2(), JpaControllerUtil.haveId(te)); |
| |
| // issue #219565 - troubles of EJB's enities CP visibility |
| FileObject file = SourceUtils.getFile(teh, parameter.getClasspathInfo()); |
| if (isEjbProjectEntity(ejbProjects, file)) { |
| ejbModuleInvolved = true; |
| LOG.log(Level.INFO, "Entity came from EJB module and mustn''t be visible on CP, entity={0}", en.getClass2()); |
| } |
| } |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| availableEntities.removeAll(selectedEntities); |
| changeSupport.fireChange(); |
| } |
| |
| private boolean isEjbProjectEntity(Set<Project> ejbProjects, FileObject fo) { |
| for (Project relatedProject : ejbProjects) { |
| if (fo.getPath().startsWith(relatedProject.getProjectDirectory().getPath())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void addChangeListener(ChangeListener listener) { |
| changeSupport.addChangeListener(listener); |
| } |
| |
| public Set<String> getAvailableEntities() { |
| return availableEntities; |
| } |
| |
| public Set<Entity> getAvailableEntityInstances() { |
| return availableEntityInstances; |
| } |
| |
| public Set<String> getWantedEntities() { |
| return wantedEntities; |
| } |
| |
| public Set<String> getSelectedEntities() { |
| return selectedEntities; |
| } |
| |
| public boolean isEjbModuleInvolved() { |
| return ejbModuleInvolved; |
| } |
| |
| public void addEntities(Set<String> entities) { |
| if (isClosureEnabled()) { |
| if (wantedEntities.addAll(entities)) { |
| try{ |
| Set<String> refEntities = getReferencedEntitiesTransitively(entities); |
| Set<String> addedEntities = new HashSet<String>(entities); |
| addedEntities.addAll(refEntities); |
| |
| selectedEntities.addAll(addedEntities); |
| referencedEntities.addAll(refEntities); |
| availableEntities.removeAll(addedEntities); |
| |
| changeSupport.fireChange(); |
| } catch (IOException ioe){ |
| Exceptions.printStackTrace(ioe); |
| } |
| } |
| } else { |
| wantedEntities.addAll(entities); |
| selectedEntities.addAll(entities); |
| availableEntities.removeAll(entities); |
| |
| changeSupport.fireChange(); |
| } |
| } |
| |
| public void removeEntities(Set<String> entities) { |
| if (isClosureEnabled()) { |
| if (wantedEntities.removeAll(entities)) { |
| redoClosure(); |
| |
| changeSupport.fireChange(); |
| } |
| } else { |
| wantedEntities.removeAll(entities); |
| selectedEntities.removeAll(entities); |
| availableEntities.addAll(entities); |
| |
| changeSupport.fireChange(); |
| } |
| } |
| |
| public void addAllEntities() { |
| wantedEntities.addAll(availableEntities); |
| |
| if (isClosureEnabled()) { |
| redoClosure(); |
| |
| changeSupport.fireChange(); |
| } else { |
| selectedEntities.addAll(wantedEntities); |
| availableEntities.clear(); |
| |
| changeSupport.fireChange(); |
| } |
| } |
| |
| public void removeAllEntities() { |
| availableEntities.addAll(selectedEntities); |
| wantedEntities.clear(); |
| selectedEntities.clear(); |
| referencedEntities.clear(); |
| |
| changeSupport.fireChange(); |
| } |
| |
| |
| /** |
| * Returns the tables transitively referenced by the contents of the tables parameter |
| * (not including tables passed in this parameter). If a table references itself, |
| * it is not added to the result. |
| */ |
| private Set<String> getReferencedEntitiesTransitively(Set<String> entities) throws IOException { |
| |
| Queue<String> entityQueue = new Queue<String>(entities); |
| Set<String> refEntities = new HashSet<String>(); |
| |
| while (!entityQueue.isEmpty()) { |
| String entity = entityQueue.poll(); |
| |
| Set<String> referenced = getReferencedEntities(entity); |
| for (String refEntity : referenced) { |
| |
| if (!refEntity.equals(entity)) { |
| refEntities.add(refEntity); |
| } |
| entityQueue.offer(refEntity); |
| } |
| } |
| |
| return refEntities; |
| } |
| |
| private Set<String> getReferencedEntities(final String entityClass) throws IOException { |
| |
| if (readHelper.getState() != State.FINISHED) { |
| return Collections.emptySet(); |
| } |
| |
| JavaSource source = model.runReadAction(new MetadataModelAction<EntityMappingsMetadata, JavaSource>() { |
| @Override |
| public JavaSource run(EntityMappingsMetadata metadata) throws Exception { |
| return metadata.createJavaSource(); |
| } |
| }); |
| |
| final Set<String> result = new HashSet<String>(); |
| |
| source.runUserActionTask(new Task<CompilationController>() { |
| @Override |
| public void run(CompilationController parameter) throws Exception { |
| List<Entity> entities = readHelper.getResult(); |
| Set<String> entitiesFqn = new HashSet<String>(); |
| for( Entity entity : entities){ |
| entitiesFqn.add( entity.getClass2()); |
| } |
| |
| TypeElement entity = parameter.getElements().getTypeElement(entityClass); |
| for (Element element : parameter.getElements().getAllMembers(entity)){ |
| if (ElementKind.METHOD.equals(element.getKind())){ |
| ExecutableType methodType = (ExecutableType)parameter.getTypes(). |
| asMemberOf((DeclaredType)entity.asType(), element); |
| TypeMirror returnType = methodType.getReturnType(); |
| addTypeMirror(result, parameter, returnType, entitiesFqn); |
| List<? extends TypeMirror> parameterTypes = |
| methodType.getParameterTypes(); |
| for (TypeMirror paramType : parameterTypes) { |
| addTypeMirror(result, parameter, paramType, entitiesFqn); |
| } |
| } |
| else if (ElementKind.FIELD.equals(element.getKind())){ |
| TypeMirror typeMirror = parameter.getTypes(). |
| asMemberOf((DeclaredType)entity.asType(), element); |
| addTypeMirror(result, parameter, typeMirror, entitiesFqn ); |
| } |
| } |
| } |
| |
| private void addTypeMirror( final Set<String> result, |
| CompilationController parameter, TypeMirror typeMirror , |
| Set<String> allEntitiesFqn ) throws Exception |
| { |
| Element typeElement = parameter.getTypes().asElement( |
| typeMirror ); |
| if ( typeElement instanceof TypeElement ){ |
| String fqn = ((TypeElement)typeElement).getQualifiedName().toString(); |
| if ( allEntitiesFqn.contains( fqn )){ |
| result.add( fqn ); |
| } |
| } |
| } |
| }, true); |
| |
| return result; |
| } |
| |
| private void redoClosure() { |
| Set<String> allEntities = new HashSet<String>(availableEntities); |
| allEntities.addAll(selectedEntities); |
| |
| referencedEntities.clear(); |
| try{ |
| referencedEntities.addAll(getReferencedEntitiesTransitively(wantedEntities)); |
| }catch (IOException ioe){ |
| Exceptions.printStackTrace(ioe); |
| } |
| |
| |
| selectedEntities.clear(); |
| selectedEntities.addAll(wantedEntities); |
| selectedEntities.addAll(referencedEntities); |
| |
| availableEntities.clear(); |
| availableEntities.addAll(allEntities); |
| availableEntities.removeAll(selectedEntities); |
| } |
| |
| public boolean isClosureEnabled() { |
| return closureEnabled; |
| } |
| |
| public void setClosureEnabled(boolean closureEnabled) { |
| if (this.closureEnabled == closureEnabled) { |
| return; |
| } |
| this.closureEnabled = closureEnabled; |
| if (closureEnabled) { |
| redoClosure(); |
| } else { |
| Set<String> allEntities = new HashSet<String>(availableEntities); |
| allEntities.addAll(selectedEntities); |
| |
| referencedEntities.clear(); |
| |
| selectedEntities.clear(); |
| selectedEntities.addAll(wantedEntities); |
| |
| availableEntities.clear(); |
| availableEntities.addAll(allEntities); |
| availableEntities.removeAll(selectedEntities); |
| } |
| changeSupport.fireChange(); |
| } |
| |
| public boolean isModelReady() { |
| return modelReady; |
| } |
| |
| void waitModelIsReady(){ |
| try { |
| Future result = model.runReadActionWhenReady(new MetadataModelAction<EntityMappingsMetadata, Boolean>() { |
| |
| @Override |
| public Boolean run(EntityMappingsMetadata metadata) throws Exception { |
| return true; |
| } |
| }); |
| result.get(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (ExecutionException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (MetadataModelException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| } |
| |
| private boolean isFieldAccess(final String entity) throws MetadataModelException, IOException { |
| Boolean result = model.runReadAction(new MetadataModelAction<EntityMappingsMetadata, Boolean>() { |
| |
| @Override |
| public Boolean run(EntityMappingsMetadata metadata) throws Exception { |
| for (Entity e : metadata.getRoot().getEntity()){ |
| if (e.getClass2().equals(entity)){ |
| return e.getAccess().equals(Entity.FIELD_ACCESS); |
| } |
| } |
| return false; |
| } |
| }); |
| |
| return result; |
| } |
| |
| Boolean haveId(String entityFqn) { |
| return fqnIdExistMap.get(entityFqn); |
| } |
| |
| Entity getEntity(String entityFqn){ |
| return fqnEntityMap.get(entityFqn); |
| } |
| |
| private Set<Project> getEjbModulesOfClasspath(ClassPath classPath) { |
| Set<Project> ejbProjects = new HashSet<Project>(); |
| for (FileObject fileObject : classPath.getRoots()) { |
| Project rootOwner = FileOwnerQuery.getOwner(fileObject); |
| if (rootOwner != null) { |
| if (Util.isEjbModule(rootOwner)) { |
| ejbProjects.add(rootOwner); |
| } |
| } |
| } |
| return ejbProjects; |
| } |
| |
| /** |
| * A simple queue. An object can only be added once, even |
| * if it has already been removed from the queue. This class could implement |
| * the {@link java.util.Queue} interface, but it doesn't because that |
| * interface has too many unneeded methods. Not private because of the tests. |
| */ |
| static final class Queue<T> { |
| |
| /** |
| * The queue. Implemented as ArrayList since will be iterated using get(). |
| */ |
| private final List<T> queue; |
| |
| /** |
| * The contents of the queue, needed in order to quickly (ideally |
| * in a constant time) tell if a table has been already added. |
| */ |
| private final Set<T> contents; |
| |
| /** |
| * The position in the queue. |
| */ |
| private int currentIndex; |
| |
| /** |
| * Creates a queue with an initial contents. |
| */ |
| public Queue(Set<T> initialContents) { |
| assert !initialContents.contains(null); |
| |
| queue = new ArrayList(initialContents); |
| contents = new HashSet(initialContents); |
| } |
| |
| /** |
| * Adds an elements to the queue if it hasn't been already added. |
| */ |
| public void offer(T element) { |
| assert element != null; |
| |
| if (!contents.contains(element)) { |
| contents.add(element); |
| queue.add(element); |
| } |
| } |
| |
| /** |
| * Returns the element at the top of the queue without removing it or null |
| * if the queue is empty. |
| */ |
| public boolean isEmpty() { |
| return currentIndex >= queue.size(); |
| } |
| |
| /** |
| * Returns and removes the elements at the top of the queue or null if |
| * the queue is empty. |
| */ |
| public T poll() { |
| T result = null; |
| if (!isEmpty()) { |
| result = queue.get(currentIndex); |
| currentIndex++; |
| } |
| return result; |
| } |
| } |
| |
| private static class EntityClosureComboBoxModel extends DefaultComboBoxModel implements ChangeListener { |
| |
| private EntityClosure entityClosure; |
| private List<String> entities = new ArrayList<String>(); |
| |
| EntityClosureComboBoxModel(EntityClosure entityClosure) { |
| this.entityClosure = entityClosure; |
| entityClosure.addChangeListener(this); |
| refresh(); |
| } |
| |
| @Override |
| public int getSize() { |
| return entities.size(); |
| } |
| |
| @Override |
| public Object getElementAt(int index) { |
| return entities.get(index); |
| } |
| |
| /** |
| * @return the fully qualified names of the entities in this model. |
| */ |
| public List<String> getEntityClasses() { |
| return entities; |
| } |
| |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| refresh(); |
| } |
| |
| private void refresh() { |
| int oldSize = getSize(); |
| entities = new ArrayList<String>(entityClosure.getAvailableEntities()); |
| Collections.sort(entities); |
| fireContentsChanged(this, 0, Math.max(oldSize, getSize())); |
| } |
| } |
| |
| |
| } |